diff --git a/src/main/java/it/inaf/oats/vospace/CreateNodeController.java b/src/main/java/it/inaf/oats/vospace/CreateNodeController.java index b3250614ff98a092b6ec8a2ea6b63e27c92d5c6c..3110cecc1b1150ac889800c102574dad9ac1301a 100644 --- a/src/main/java/it/inaf/oats/vospace/CreateNodeController.java +++ b/src/main/java/it/inaf/oats/vospace/CreateNodeController.java @@ -1,6 +1,7 @@ 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 net.ivoa.xml.vospace.v2.Node; import org.springframework.http.MediaType; @@ -13,6 +14,8 @@ import org.springframework.web.bind.annotation.PutMapping; import it.inaf.oats.vospace.exception.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import net.ivoa.xml.vospace.v2.Property; +import java.util.List; @RestController public class CreateNodeController extends BaseNodeController { @@ -67,6 +70,25 @@ public class CreateNodeController extends BaseNodeController { if(!NodeUtils.checkIfWritable(parentNode, principal.getName(), principal.getGroups())) { throw new PermissionDeniedException(path); } + + // Check if node creator property is set. If not set it according to + // token. In case of creator mistmatch between node and token throw + // exception + + String creator = NodeProperties.getNodePropertyByURI( + node, NodeProperties.CREATOR_URI); + + if(creator == null) + { + Property creatorProperty = new Property(); + creatorProperty.setUri(NodeProperties.CREATOR_URI); + creatorProperty.setValue(principal.getName()); + node.getProperties().add(creatorProperty); + } else { + if(!creator.equals(principal.getName())) + // maybe a more specific exception would be more appropriate? + throw new PermissionDeniedException(path); + } nodeDao.createNode(node); diff --git a/src/main/java/it/inaf/oats/vospace/ListNodeController.java b/src/main/java/it/inaf/oats/vospace/ListNodeController.java index 4ac5fc58da6b04966162f337894361797793db9b..7dd303f42c02a101c3334d6bf8604c8798434326 100644 --- a/src/main/java/it/inaf/oats/vospace/ListNodeController.java +++ b/src/main/java/it/inaf/oats/vospace/ListNodeController.java @@ -13,21 +13,36 @@ import javax.servlet.http.HttpServletRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.MediaType; +import it.inaf.ia2.aa.data.User; +import it.inaf.oats.vospace.datamodel.NodeUtils; +import java.util.Optional; +import it.inaf.oats.vospace.exception.PermissionDeniedException; @RestController public class ListNodeController extends BaseNodeController { private static final Logger LOG = LoggerFactory.getLogger(ListNodeController.class); - + @Autowired private NodeDAO nodeDAO; @GetMapping(value = {"/nodes", "/nodes/**"}, produces = {MediaType.APPLICATION_XML_VALUE, MediaType.APPLICATION_JSON_VALUE, MediaType.TEXT_XML_VALUE}) - public ResponseEntity<Node> listNode(HttpServletRequest request) { + public ResponseEntity<Node> listNode(HttpServletRequest request, User principal) { String path = getPath(); LOG.debug("listNode called for path {}", path); - return ResponseEntity.ok(nodeDAO.listNode(path) - .orElseThrow(() -> new NodeNotFoundException(path))); + + Optional<Node> optNode = nodeDAO.listNode(path); + + if (optNode.isEmpty()) { + throw new NodeNotFoundException(path); + } else { + if (!NodeUtils.checkIfReadable( + optNode.get(), principal.getName(), principal.getGroups())) { + throw new PermissionDeniedException(path); + } + } + + return ResponseEntity.ok(optNode.get()); } } diff --git a/src/test/java/it/inaf/oats/vospace/CreateNodeControllerTest.java b/src/test/java/it/inaf/oats/vospace/CreateNodeControllerTest.java index 27058b09be18a34e4ee6fd68f38cfc8fb4567e73..baeef5d3ab6eea039951c222448ff44e8e9f4c7f 100644 --- a/src/test/java/it/inaf/oats/vospace/CreateNodeControllerTest.java +++ b/src/test/java/it/inaf/oats/vospace/CreateNodeControllerTest.java @@ -5,6 +5,7 @@ import java.io.InputStream; import java.net.URI; import java.nio.charset.StandardCharsets; import net.ivoa.xml.vospace.v2.Property; +import it.inaf.oats.vospace.datamodel.NodeProperties; import net.ivoa.xml.vospace.v2.UnstructuredDataNode; import org.junit.jupiter.api.Test; import static org.mockito.ArgumentMatchers.argThat; @@ -271,6 +272,57 @@ public class CreateNodeControllerTest { verifyArguments(); verify(nodeDao, times(1)).createNode(any()); } + + @Test + public void testWriteOwnerAbsent() throws Exception { + String requestBody = + getResourceFileContent("create-unstructured-data-node.xml"); + + when(nodeDao.listNode(eq("/"))) + .thenReturn(Optional.of(getContainerParentNodeWithCreator("/"))); + + // no node creator specified in xml file + + mockMvc.perform(put("/nodes/mydata1") + .header("Authorization", "Bearer user2_token") + .content(requestBody) + .contentType(MediaType.APPLICATION_XML) + .accept(MediaType.APPLICATION_XML)) + .andDo(print()) + .andExpect(status().is2xxSuccessful()); + + // assert creator properties now matches user2 + verify(nodeDao, times(1)).createNode(argThat(node->{ + UnstructuredDataNode udn = (UnstructuredDataNode) node; + String creator = NodeProperties.getNodePropertyByURI( + udn, NodeProperties.CREATOR_URI); + return (creator != null && creator.equals("user2")); + } + )); + + } + + @Test + public void testWriteOwnerMismatch() throws Exception { + String requestBody = + getResourceFileContent("create-unstructured-data-node-user1.xml"); + + when(nodeDao.listNode(eq("/"))) + .thenReturn(Optional.of(getContainerParentNodeWithCreator("/"))); + + // no node creator specified in xml file + + mockMvc.perform(put("/nodes/mydata1") + .header("Authorization", "Bearer user2_token") + .content(requestBody) + .contentType(MediaType.APPLICATION_XML) + .accept(MediaType.APPLICATION_XML)) + .andDo(print()) + .andExpect(status().is4xxClientError()); + + // assert createNode is not called + verify(nodeDao, times(0)).createNode(any()); + } @Test public void testSubPath() throws Exception { diff --git a/src/test/java/it/inaf/oats/vospace/ListNodeControllerTest.java b/src/test/java/it/inaf/oats/vospace/ListNodeControllerTest.java index 47988ca6b974c7c28c1c72e1ee9180b67c270d62..26c390a0ca9f835a015c9a059631fed5766ac834 100644 --- a/src/test/java/it/inaf/oats/vospace/ListNodeControllerTest.java +++ b/src/test/java/it/inaf/oats/vospace/ListNodeControllerTest.java @@ -1,11 +1,13 @@ package it.inaf.oats.vospace; import static it.inaf.oats.vospace.VOSpaceXmlTestUtil.loadDocument; +import it.inaf.oats.vospace.datamodel.NodeProperties; 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 net.ivoa.xml.vospace.v2.Property; import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; import static org.mockito.ArgumentMatchers.eq; @@ -16,7 +18,10 @@ 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.http.HttpHeaders; import org.springframework.http.MediaType; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; import org.springframework.test.web.servlet.MockMvc; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; @@ -25,6 +30,8 @@ import org.w3c.dom.Document; @SpringBootTest @AutoConfigureMockMvc +@ContextConfiguration(classes = {TokenFilterConfig.class}) +@TestPropertySource(properties = "spring.main.allow-bean-definition-overriding=true") public class ListNodeControllerTest { private static final String URI_PREFIX = "vos://example.com!vospace"; @@ -77,10 +84,39 @@ public class ListNodeControllerTest { .accept(MediaType.APPLICATION_XML)) .andExpect(status().isNotFound()); } + + @Test + public void testPermissionDeniedUser() throws Exception { + Node node = getDataNodeByOwnership("user2","group1"); + + when(dao.listNode(eq("/mynode"))).thenReturn(Optional.of(node)); + + mockMvc.perform(get("/nodes/mynode") + .header("Authorization", "Bearer user1_token") + .accept(MediaType.APPLICATION_XML)) + .andExpect(status().is4xxClientError()); + } + + @Test + public void testGrantedByGroup() throws Exception { + Node node = getDataNodeByOwnership("user1","group1"); + + when(dao.listNode(eq("/mynode"))).thenReturn(Optional.of(node)); + + mockMvc.perform(get("/nodes/mynode") + .header("Authorization", "Bearer user2_token") + .accept(MediaType.APPLICATION_XML)) + .andExpect(status().is2xxSuccessful()); + } private Optional<Node> getRootNode() { ContainerNode root = new ContainerNode(); root.setUri(URI_PREFIX + "/"); + Property publicProperty = new Property(); + publicProperty.setUri(NodeProperties.PUBLIC_READ_URI); + publicProperty.setValue("true"); + root.getProperties().add(publicProperty); + root.getNodes().add(getDataNode()); return Optional.of(root); } @@ -88,6 +124,29 @@ public class ListNodeControllerTest { private Node getDataNode() { DataNode node = new DataNode(); node.setUri(URI_PREFIX + "/mynode"); + Property publicProperty = new Property(); + publicProperty.setUri(NodeProperties.PUBLIC_READ_URI); + publicProperty.setValue("true"); + node.getProperties().add(publicProperty); + return node; } + + private Node getDataNodeByOwnership(String ownerID, String group) + { + DataNode node = new DataNode(); + node.setUri(URI_PREFIX + "/mynode"); + // Set owner + Property creatorProperty = new Property(); + creatorProperty.setUri(NodeProperties.CREATOR_URI); + creatorProperty.setValue(ownerID); + node.getProperties().add(creatorProperty); + // set group + Property readGroupProperty = new Property(); + readGroupProperty.setUri(NodeProperties.GROUP_READ_URI); + readGroupProperty.setValue(group); + node.getProperties().add(readGroupProperty); + + return node; + } } diff --git a/src/test/resources/create-unstructured-data-node-user1.xml b/src/test/resources/create-unstructured-data-node-user1.xml new file mode 100644 index 0000000000000000000000000000000000000000..82d50ca847b3d3d16d47beba0412c57e18959521 --- /dev/null +++ b/src/test/resources/create-unstructured-data-node-user1.xml @@ -0,0 +1,11 @@ +<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:UnstructuredDataNode" uri="vos://example.com!vospace/mydata1"> + <vos:properties> + <vos:property uri="ivo://ivoa.net/vospace/core#description">test value</vos:property> + <vos:property uri="ivo://ivoa.net/vospace/core#creator">user1</vos:property> + </vos:properties> + <vos:accepts/> + <vos:provides/> + <vos:capabilities/> +</vos:node> \ No newline at end of file diff --git a/src/test/resources/create-unstructured-data-node.xml b/src/test/resources/create-unstructured-data-node.xml index 1c8e8e6fe08c12300d9ddcc6daa3b6cbedba65da..11f10fcfa9f85d3fece0d30f7e457238e11062c4 100644 --- a/src/test/resources/create-unstructured-data-node.xml +++ b/src/test/resources/create-unstructured-data-node.xml @@ -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:UnstructuredDataNode" uri="vos://example.com!vospace/mydata1"> <vos:properties> - <vos:property uri="ivo://ivoa.net/vospace/core#description">test value</vos:property> + <vos:property uri="ivo://ivoa.net/vospace/core#description">test value</vos:property> </vos:properties> <vos:accepts/> <vos:provides/>