From 6e7eae34f76c0f2adc3a0ac94f1a8c9da6599b11 Mon Sep 17 00:00:00 2001 From: Sonia Zorba <sonia.zorba@inaf.it> Date: Tue, 9 Mar 2021 12:30:37 +0100 Subject: [PATCH] Moved setNode view checks from DAO to Controller; Fixed unit tests and added DAO setNode test --- .../inaf/oats/vospace/SetNodeController.java | 114 +++++++++++--- .../oats/vospace/persistence/NodeDAO.java | 145 +----------------- .../oats/vospace/SetNodeControllerTest.java | 39 ++--- .../oats/vospace/persistence/NodeDAOTest.java | 25 +++ .../resources/modify-data-node-1-views.xml | 4 +- .../resources/modify-data-node-1_type.xml | 2 +- ...y-data-node-description-leaving-views.xml} | 8 +- ....xml => modify-data-node-having-views.xml} | 8 +- 8 files changed, 152 insertions(+), 193 deletions(-) rename src/test/resources/{modify-data-node_description-leaving-views_and_protocols.xml => modify-data-node-description-leaving-views.xml} (71%) rename src/test/resources/{modify-data-node-having-views_and_protocols.xml => modify-data-node-having-views.xml} (67%) diff --git a/src/main/java/it/inaf/oats/vospace/SetNodeController.java b/src/main/java/it/inaf/oats/vospace/SetNodeController.java index ea3147c..d80bd72 100644 --- a/src/main/java/it/inaf/oats/vospace/SetNodeController.java +++ b/src/main/java/it/inaf/oats/vospace/SetNodeController.java @@ -1,6 +1,5 @@ package it.inaf.oats.vospace; - import it.inaf.ia2.aa.data.User; import it.inaf.oats.vospace.datamodel.NodeUtils; import it.inaf.oats.vospace.exception.LinkFoundException; @@ -9,12 +8,12 @@ import it.inaf.oats.vospace.exception.PermissionDeniedException; import it.inaf.oats.vospace.persistence.NodeDAO; import java.util.List; import javax.servlet.http.HttpServletRequest; +import net.ivoa.xml.vospace.v2.DataNode; import net.ivoa.xml.vospace.v2.Node; +import net.ivoa.xml.vospace.v2.View; 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.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; @@ -23,15 +22,12 @@ import org.springframework.web.bind.annotation.RestController; @RestController public class SetNodeController extends BaseNodeController { - + private static final Logger LOG = LoggerFactory.getLogger(SetNodeController.class); @Autowired private NodeDAO nodeDao; - @Value("${vospace-authority}") - private String authority; - @PostMapping(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}) @@ -39,32 +35,108 @@ public class SetNodeController extends BaseNodeController { String path = getPath(); LOG.debug("setNode called for path {}", path); - + //The service SHALL throw a HTTP 404 status code including a NodeNotFound //fault in the entity-body if the target Node does not exist Node toBeModifiedNode = nodeDao.listNode(path) - .orElseThrow(() -> new NodeNotFoundException(path)); - - + .orElseThrow(() -> new NodeNotFoundException(path)); + // The service SHALL throw a HTTP 403 status code including a PermissionDenied fault // in the entity-body if the user does not have permissions to perform the operation - if(!NodeUtils.checkIfWritable(toBeModifiedNode, principal.getName(), principal.getGroups())) { + if (!NodeUtils.checkIfWritable(toBeModifiedNode, principal.getName(), principal.getGroups())) { throw new PermissionDeniedException(path); } - + // The service SHALL throw a HTTP 403 status code including a PermissionDenied fault // in the entity-body if the request attempts to modify a read-only Property. // This method cannot be used to modify the Node type. String storedNodeType = toBeModifiedNode.getType(); String newNodeType = node.getType(); - if(!storedNodeType.equals(newNodeType)) { + if (!storedNodeType.equals(newNodeType)) { LOG.debug("setNode trying to modify type. Stored ", storedNodeType + ", requested " + newNodeType); - throw new PermissionDeniedException(path); + throw new PermissionDeniedException(path); + } + + // This method cannot be used to modify the accepts or provides list of Views for the Node. + // Only DataNodes has Views (see VOSpace Data Model) + if (node instanceof DataNode) { + checkViews((DataNode) node, (DataNode) toBeModifiedNode); + } + + //The service SHOULD throw a HTTP 500 status code including an InternalFault fault + // in the entity-body if the operation fails + // Done in NodeDAO + // to be fixed + // A HTTP 200 status code and a Node representation in the entity-body The full + // expanded record for the node SHALL be returned, including any xsi:type + // specific extensions. + return ResponseEntity.ok(nodeDao.setNode(node)); + } + + private void checkViews(DataNode requestedDataNode, DataNode savedDataNode) { + + checkAcceptsView(requestedDataNode, savedDataNode); + checkProvidesView(requestedDataNode, savedDataNode); + } + + private void checkAcceptsView(DataNode requestedDataNode, DataNode savedDataNode) { + List<View> requestedAcceptedViews = requestedDataNode.getAccepts(); + List<View> savedAcceptedViews = savedDataNode.getAccepts(); + + // If views are present it is necessary to perform the check, otherwise + // it means that the user is not going to change them + if (!requestedAcceptedViews.isEmpty() && !checkIfViewsAreEquals(requestedAcceptedViews, savedAcceptedViews)) { + LOG.debug("setNode trying to modify accepted Views."); + throw new PermissionDeniedException("Trying to modify accepted views"); } - - Node result = nodeDao.setNode(node).orElseThrow(() -> new PermissionDeniedException("")); - - return ResponseEntity.ok(result); - } - + } + + private void checkProvidesView(DataNode requestedDataNode, DataNode savedDataNode) { + + List<View> requestedProvidedViews = requestedDataNode.getProvides(); + List<View> savedProvidedViews = savedDataNode.getProvides(); + + // If views are present it is necessary to perform the check, otherwise + // it means that the user is not going to change them + if (!requestedProvidedViews.isEmpty() && !checkIfViewsAreEquals(requestedProvidedViews, savedProvidedViews)) { + LOG.debug("setNode trying to modify provided Views."); + throw new PermissionDeniedException("Trying to modify provided views"); + } + } + + private boolean checkIfViewsAreEquals(List<View> viewsA, List<View> viewsB) { + + boolean viewsAreEqual = true; + if ((viewsA == null || viewsA.isEmpty()) + && (viewsB == null || viewsB.isEmpty())) { + return true; + } + + if ((viewsA == null || viewsA.isEmpty()) + || (viewsB == null || viewsB.isEmpty())) { + return false; + } + + if (viewsA.size() != viewsB.size()) { + return false; + } + + for (int i = 0; i < viewsA.size(); i++) { + String viewUriA = viewsA.get(i).getUri(); + boolean found = false; + for (int j = 0; j < viewsB.size(); j++) { + String viewUriB = viewsB.get(i).getUri(); + if (viewUriA.compareTo(viewUriB) == 0) { + found = true; + } + } + + if (found == false) { + viewsAreEqual = false; + break; + } + } + + return viewsAreEqual; + } } 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 ab8095e..d843da7 100644 --- a/src/main/java/it/inaf/oats/vospace/persistence/NodeDAO.java +++ b/src/main/java/it/inaf/oats/vospace/persistence/NodeDAO.java @@ -3,10 +3,7 @@ package it.inaf.oats.vospace.persistence; import it.inaf.oats.vospace.DeleteNodeController; 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.InternalFaultException; -import it.inaf.oats.vospace.exception.NodeNotFoundException; -import it.inaf.oats.vospace.exception.PermissionDeniedException; import java.sql.Array; import net.ivoa.xml.vospace.v2.Node; import java.sql.PreparedStatement; @@ -23,7 +20,6 @@ import javax.sql.DataSource; import net.ivoa.xml.vospace.v2.ContainerNode; import net.ivoa.xml.vospace.v2.DataNode; import net.ivoa.xml.vospace.v2.Property; -import net.ivoa.xml.vospace.v2.StructuredDataNode; import net.ivoa.xml.vospace.v2.View; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -124,42 +120,10 @@ public class NodeDAO { } - public Optional<Node> setNode(Node newNode) { + public Node setNode(Node newNode) { - // Verify that the node is in the database String vosPath = NodeUtils.getVosPath(newNode); - // List<NodePaths> paths = getNodePathsFromDB(nodeURI); - - // This method cannot be used to modify the accepts or provides list of Views for the Node. - // Only DataNodes has Views (see VOSpace Data Model - // Check if trying to change views getting Views (if present) of the modified node - if (newNode instanceof DataNode) { - DataNode dataNode = (DataNode)newNode; - List<View> requestedAcceptedViews = dataNode.getAccepts(); - List<View> savedAcceptedViews = getAcceptedViewsFromDB(vosPath); - // Get the Views of the saved node - List<View> requestedProvidedViews = dataNode.getProvides(); - List<View> savedProvidedViews = getProvidedViewsFromDB(vosPath); - - if (!requestedAcceptedViews.isEmpty()) { - //se sono non nulle, devo fare i controlli, altrimenti di sicuro l'utente non sta chiedendo di cambiarle - // Check if requestedAcceptedViews are different from the already stored - if (!checkIfViewsAreEquals(requestedAcceptedViews,savedAcceptedViews)) { - LOG.debug("setNode trying to modify accepted Views."); - throw new PermissionDeniedException("Trying to modify accepted views"); - } - } - - if (!requestedProvidedViews.isEmpty()) { - //se sono non nulle, devo fare i controlli, altrimenti di sicuro l'utente non sta chiedendo di cambiarle - // Check if requestedAcceptedViews are different from the already stored - if (!checkIfViewsAreEquals(requestedProvidedViews,savedProvidedViews)) { - LOG.debug("setNode trying to modify provided Views."); - throw new PermissionDeniedException("Trying to modify provided views"); - } - } - } - + StringBuilder sb = new StringBuilder(); sb.append("UPDATE node"); @@ -177,40 +141,9 @@ public class NodeDAO { return ps; }); - return Optional.of(newNode); - - + return newNode; } - - - private Optional<Node> getNode(String path) { - - String sql = "SELECT node_id, type, async_trans, sticky, busy_state, creator_id, group_read, group_write,\n" - + "is_public, content_length, created_on, last_modified, accept_views, provide_views \n" - + "FROM node WHERE path = ?"; - - List<Node> nodes = jdbcTemplate.query(conn -> { - PreparedStatement ps = conn.prepareStatement(sql); - ps.setString(1, path); - return ps; - }, (row, index) -> { - return getNodeFromResultSet(row); - }); - - if (nodes.isEmpty()) { - return Optional.empty(); - } - // Query returns the node (can be container or data) - Node node = nodes.get(0); - - return Optional.of(node); - - } - - - - 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 || '"; @@ -424,79 +357,9 @@ public class NodeDAO { } } - - - private boolean checkIfViewsAreEquals(List<View> viewsA, List<View> viewsB) { - - boolean viewsAreEqual = true; - if ((viewsA == null || viewsA.isEmpty()) && - (viewsB == null || viewsB.isEmpty())) - return true; - - if ((viewsA == null || viewsA.isEmpty()) || - (viewsB == null || viewsB.isEmpty())) - return false; - - if( viewsA.size() != viewsB.size()) - return false; - - for(int i = 0; i < viewsA.size(); i++) { - String viewUriA = viewsA.get(i).getUri(); - boolean found = false; - for(int j = 0; j < viewsB.size(); j++) { - String viewUriB = viewsB.get(i).getUri(); - if (viewUriA.compareTo(viewUriB) == 0) { - found = true; - } - } - - if(found == false) { - viewsAreEqual = false; - break; - } - } - - return viewsAreEqual; - - } - - - private List<View> getAcceptedViewsFromDB(String nodePath) { - - return getViewsFromDB(nodePath, "accept_views"); - - } - - - private List<View> getProvidedViewsFromDB(String nodePath) { - - return getViewsFromDB(nodePath, "provide_views"); - - } - - - private List<View> getViewsFromDB(String nodePath, String viewType) { - - String sql = "SELECT accept_views FROM node WHERE node_id = " - + "(SELECT node_id FROM node_vos_path WHERE vos_path = ?)"; - - Array results = jdbcTemplate.query(sql, ps -> { - ps.setString(1, nodePath); - }, (rs) -> { - if(rs.next()) - return rs.getArray("accept_views"); - else - return null; - }); - - return getViews(results); - - } - private List<NodePaths> getNodePathsFromDB(String nodeURI) { - - //String nodeURI = myNode.getUri(); + String path = nodeURI.replaceAll("vos://[^/]+", ""); String parentPath = NodeUtils.getParentPath(path); diff --git a/src/test/java/it/inaf/oats/vospace/SetNodeControllerTest.java b/src/test/java/it/inaf/oats/vospace/SetNodeControllerTest.java index a7b490c..f010df8 100644 --- a/src/test/java/it/inaf/oats/vospace/SetNodeControllerTest.java +++ b/src/test/java/it/inaf/oats/vospace/SetNodeControllerTest.java @@ -9,9 +9,9 @@ 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 net.ivoa.xml.vospace.v2.View; import org.junit.jupiter.api.Test; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; @@ -62,7 +62,7 @@ public class SetNodeControllerTest { private Node getWritableDataNode(String path) { DataNode node = new DataNode(); - List nodeProperties = node.getProperties(); + List<Property> nodeProperties = node.getProperties(); Property groupWriteProp = new Property(); groupWriteProp.setUri(NodeProperties.GROUP_WRITE_URI); groupWriteProp.setValue("group1"); @@ -73,7 +73,7 @@ public class SetNodeControllerTest { private Node getWritableDataNodeWithAcceptsAndProvides(String path) { DataNode node = new DataNode(); - List nodeProperties = node.getProperties(); + List<Property> nodeProperties = node.getProperties(); Property groupWriteProp = new Property(); groupWriteProp.setUri(NodeProperties.GROUP_WRITE_URI); groupWriteProp.setValue("group1"); @@ -86,18 +86,22 @@ public class SetNodeControllerTest { nodeProperties.add(groupWriteProp); nodeProperties.add(groupReadProp); nodeProperties.add(descriptionProp); - List accepts = new ArrayList(); - accepts.add("ivo://ivoa.net/vospace/core#httpput"); - accepts.add("ivo://ivoa.net/vospace/core#defaultview"); + List<View> accepts = new ArrayList<>(); + accepts.add(getView("ivo://ivoa.net/vospace/core#defaultview")); node.setAccepts(accepts); - List provides = new ArrayList(); - provides.add("ivo://ivoa.net/vospace/core#httpput"); - provides.add("ivo://ivoa.net/vospace/core#defaultview"); - node.setAccepts(provides); + List<View> provides = new ArrayList<>(); + provides.add(getView("ivo://ivoa.net/vospace/core#defaultview")); + node.setProvides(provides); node.setUri(URI_PREFIX + path); return node; } + private View getView(String uri) { + View view = new View(); + view.setUri(uri); + return view; + } + /* Test case: try to modify node type. Forbidden. @@ -153,7 +157,7 @@ public class SetNodeControllerTest { @Test public void testModifyAcceptsAndProvides() throws Exception { - String requestBody = getResourceFileContent("modify-data-node-1-views_and_protocols.xml"); + String requestBody = getResourceFileContent("modify-data-node-having-views.xml"); // Create node when(nodeDao.listNode(eq("/"))) @@ -167,7 +171,6 @@ public class SetNodeControllerTest { .accept(MediaType.APPLICATION_XML)) .andDo(print()) .andExpect(status().isForbidden()); - } /* Test case: @@ -177,7 +180,7 @@ public class SetNodeControllerTest { @Test public void testModifyNodeHavingAcceptsAndProvides() throws Exception { - String requestBody = getResourceFileContent("modify-data-node-having-views_and_protocols.xml"); + String requestBody = getResourceFileContent("modify-data-node-having-views.xml"); // Create node when(nodeDao.listNode(eq("/"))) @@ -191,7 +194,6 @@ public class SetNodeControllerTest { .accept(MediaType.APPLICATION_XML)) .andDo(print()) .andExpect(status().isForbidden()); - } /* Test case: @@ -200,13 +202,15 @@ public class SetNodeControllerTest { */ @Test public void testModifyNodeLeavingAcceptsAndProvides() throws Exception { - String requestBody = getResourceFileContent("modify-data-node_description-leaving-views_and_protocols.xml"); + String requestBody = getResourceFileContent("modify-data-node-description-leaving-views.xml"); + Node node = getWritableDataNodeWithAcceptsAndProvides("/mydata1"); + // Create node when(nodeDao.listNode(eq("/"))) .thenReturn(Optional.of(getContainerParentNode("/"))); - when(nodeDao.listNode(eq("/mydata1"))).thenReturn(Optional.of(getWritableDataNodeWithAcceptsAndProvides("/mydata1"))); - when(nodeDao.setNode(any())).thenReturn(Optional.of(getWritableDataNodeWithAcceptsAndProvides("/mydata1"))); + when(nodeDao.listNode(eq("/mydata1"))).thenReturn(Optional.of(node)); + when(nodeDao.setNode(any())).thenReturn(node); mockMvc.perform(post("/nodes/mydata1") .header("Authorization", "Bearer user2_token") @@ -215,7 +219,6 @@ public class SetNodeControllerTest { .accept(MediaType.APPLICATION_XML)) .andDo(print()) .andExpect(status().isOk()); - } } diff --git a/src/test/java/it/inaf/oats/vospace/persistence/NodeDAOTest.java b/src/test/java/it/inaf/oats/vospace/persistence/NodeDAOTest.java index eb27450..16f1176 100644 --- a/src/test/java/it/inaf/oats/vospace/persistence/NodeDAOTest.java +++ b/src/test/java/it/inaf/oats/vospace/persistence/NodeDAOTest.java @@ -110,6 +110,31 @@ public class NodeDAOTest { } assertTrue(exception); } + + @Test + public void testSetNode() { + + Property publicReadProperty = new Property(); + publicReadProperty.setUri(NodeProperties.PUBLIC_READ_URI); + publicReadProperty.setValue(String.valueOf(false)); + + Node node = new DataNode(); + node.setUri("vos://example.com!vospace/mydata3"); + node.getProperties().add(publicReadProperty); + dao.createNode(node); + + node = dao.listNode("/mydata3").get(); + assertEquals("false", NodeProperties.getNodePropertyByURI(node, NodeProperties.PUBLIC_READ_URI)); + + node.getProperties().clear(); + publicReadProperty.setValue(String.valueOf(true)); + node.getProperties().add(publicReadProperty); + + dao.setNode(node); + + node = dao.listNode("/mydata3").get(); + assertEquals("true", NodeProperties.getNodePropertyByURI(node, NodeProperties.PUBLIC_READ_URI)); + } private String getProperty(Node node, String uri) { for (Property property : node.getProperties()) { diff --git a/src/test/resources/modify-data-node-1-views.xml b/src/test/resources/modify-data-node-1-views.xml index 04eba6f..7692f68 100644 --- a/src/test/resources/modify-data-node-1-views.xml +++ b/src/test/resources/modify-data-node-1-views.xml @@ -2,10 +2,10 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:vos="http://www.ivoa.net/xml/VOSpace/v2.0" xsi:type="vos:DataNode" uri="vos://example.com!vospace/mydata1"> <vos:properties> - <vos:property uri="ivo://ivoa.net/vospace/core#description">Data node 1, no views or protocols</vos:property> + <vos:property uri="ivo://ivoa.net/vospace/core#description">Data node 1, no views</vos:property> </vos:properties> <vos:accepts> - <vos:view uri="ivo://ivoa.net/vospace/core#anyview"/> + <vos:view uri="ivo://ivoa.net/vospace/core#anyview"/> </vos:accepts> <vos:provides/> <vos:capabilities/> diff --git a/src/test/resources/modify-data-node-1_type.xml b/src/test/resources/modify-data-node-1_type.xml index 730d8fd..d1cd54a 100644 --- a/src/test/resources/modify-data-node-1_type.xml +++ b/src/test/resources/modify-data-node-1_type.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:LinkNode" uri="vos://example.com!vospace/mydata1"> <vos:properties> - <vos:property uri="ivo://ivoa.net/vospace/core#description">Data node 1, no views or protocols</vos:property> + <vos:property uri="ivo://ivoa.net/vospace/core#description">Data node 1, no views</vos:property> </vos:properties> <vos:accepts/> <vos:provides/> diff --git a/src/test/resources/modify-data-node_description-leaving-views_and_protocols.xml b/src/test/resources/modify-data-node-description-leaving-views.xml similarity index 71% rename from src/test/resources/modify-data-node_description-leaving-views_and_protocols.xml rename to src/test/resources/modify-data-node-description-leaving-views.xml index 8d0c593..31139f1 100644 --- a/src/test/resources/modify-data-node_description-leaving-views_and_protocols.xml +++ b/src/test/resources/modify-data-node-description-leaving-views.xml @@ -2,18 +2,16 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:vos="http://www.ivoa.net/xml/VOSpace/v2.0" xsi:type="vos:DataNode" uri="vos://example.com!vospace/mydata1"> <vos:properties> - <vos:property uri="ivo://ivoa.net/vospace/core#description">Data node 1, views and protocols</vos:property> + <vos:property uri="ivo://ivoa.net/vospace/core#description">Data node 1, views</vos:property> <vos:property uri="ivo://ivoa.net/vospace/core#groupread">group1 group2</vos:property> <vos:property uri="ivo://ivoa.net/vospace/core#groupwrite">group1</vos:property> <vos:property uri="ivo://ivoa.net/vospace/core#publicread">true</vos:property> </vos:properties> <vos:accepts> - <vos:property uri="ivo://ivoa.net/vospace/core#httpput"/> - <vos:view uri="ivo://ivoa.net/vospace/core#defaultview"/> + <vos:view uri="ivo://ivoa.net/vospace/core#defaultview"/> </vos:accepts> <vos:provides> - <vos:property uri="ivo://ivoa.net/vospace/core#httpput"/> - <vos:view uri="ivo://ivoa.net/vospace/core#defaultview"/> + <vos:view uri="ivo://ivoa.net/vospace/core#defaultview"/> </vos:provides> <vos:capabilities/> </vos:node> diff --git a/src/test/resources/modify-data-node-having-views_and_protocols.xml b/src/test/resources/modify-data-node-having-views.xml similarity index 67% rename from src/test/resources/modify-data-node-having-views_and_protocols.xml rename to src/test/resources/modify-data-node-having-views.xml index c7e4e1d..2e106d9 100644 --- a/src/test/resources/modify-data-node-having-views_and_protocols.xml +++ b/src/test/resources/modify-data-node-having-views.xml @@ -2,17 +2,15 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:vos="http://www.ivoa.net/xml/VOSpace/v2.0" xsi:type="vos:DataNode" uri="vos://example.com!vospace/mydata1"> <vos:properties> - <vos:property uri="ivo://ivoa.net/vospace/core#description">Data node 1, views and protocols</vos:property> + <vos:property uri="ivo://ivoa.net/vospace/core#description">Data node 1, views</vos:property> <vos:property uri="ivo://ivoa.net/vospace/core#groupread">group1</vos:property> <vos:property uri="ivo://ivoa.net/vospace/core#publicread">true</vos:property> </vos:properties> <vos:accepts> - <vos:property uri="ivo://ivoa.net/vospace/core#httpget"/> - <vos:view uri="ivo://ivoa.net/vospace/core#defaultview"/> + <vos:view uri="ivo://ivoa.net/vospace/core#anyview"/> </vos:accepts> <vos:provides> - <vos:property uri="ivo://ivoa.net/vospace/core#httpget"/> - <vos:view uri="ivo://ivoa.net/vospace/core#defaultview"/> + <vos:view uri="ivo://ivoa.net/vospace/core#anyview"/> </vos:provides> <vos:capabilities/> </vos:node> -- GitLab