From dcea92caaef1a0483fe5cef239ba03f038a6673c Mon Sep 17 00:00:00 2001
From: Nicola Fulvio Calabria <nicola.calabria@inaf.it>
Date: Sat, 28 Aug 2021 20:34:25 +0200
Subject: [PATCH] Added LinkNode support to pullFromVoSpace

---
 .../inaf/oats/vospace/BaseNodeController.java | 18 ++++++++++++-
 .../oats/vospace/CreateNodeController.java    | 16 ++---------
 .../inaf/oats/vospace/SetNodeController.java  |  4 +++
 .../java/it/inaf/oats/vospace/UriService.java | 27 +++++++++++++++----
 4 files changed, 45 insertions(+), 20 deletions(-)

diff --git a/src/main/java/it/inaf/oats/vospace/BaseNodeController.java b/src/main/java/it/inaf/oats/vospace/BaseNodeController.java
index ddf00d9..1b317c3 100644
--- a/src/main/java/it/inaf/oats/vospace/BaseNodeController.java
+++ b/src/main/java/it/inaf/oats/vospace/BaseNodeController.java
@@ -6,15 +6,21 @@
 package it.inaf.oats.vospace;
 
 import it.inaf.oats.vospace.datamodel.NodeUtils;
+import it.inaf.oats.vospace.exception.InvalidArgumentException;
 import it.inaf.oats.vospace.exception.InvalidURIException;
 import javax.servlet.http.HttpServletRequest;
+import net.ivoa.xml.vospace.v2.LinkNode;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
 
 public abstract class BaseNodeController {
 
     @Autowired
     private HttpServletRequest servletRequest;
-
+    
+    @Value("${vospace-authority}")
+    protected String authority;     
+    
     protected String getPath() {       
         String requestURL = servletRequest.getRequestURL().toString();
         try {
@@ -27,4 +33,14 @@ public abstract class BaseNodeController {
     protected String getParentPath(String path) {
         return NodeUtils.getParentPath(path);
     }
+    
+    protected void validateInternalLinkNode(LinkNode linkNode) {
+        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);
+    }
 }
diff --git a/src/main/java/it/inaf/oats/vospace/CreateNodeController.java b/src/main/java/it/inaf/oats/vospace/CreateNodeController.java
index 759af9f..e115d15 100644
--- a/src/main/java/it/inaf/oats/vospace/CreateNodeController.java
+++ b/src/main/java/it/inaf/oats/vospace/CreateNodeController.java
@@ -6,7 +6,6 @@
 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;
@@ -17,7 +16,6 @@ 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 {
@@ -27,9 +25,6 @@ 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})
@@ -40,7 +35,7 @@ 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);
+        String decodedURIPathFromNode = URIUtils.returnVosPathFromNodeURI(node.getUri(), this.authority);
 
         LOG.debug("createNodeController URI: {} decoded as {}", node.getUri(), decodedURIPathFromNode);
 
@@ -59,14 +54,7 @@ public class CreateNodeController extends BaseNodeController {
     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);
+            this.validateInternalLinkNode((LinkNode) node);
         }
 
     }
diff --git a/src/main/java/it/inaf/oats/vospace/SetNodeController.java b/src/main/java/it/inaf/oats/vospace/SetNodeController.java
index 91b6d7f..88621d4 100644
--- a/src/main/java/it/inaf/oats/vospace/SetNodeController.java
+++ b/src/main/java/it/inaf/oats/vospace/SetNodeController.java
@@ -13,6 +13,7 @@ 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.LinkNode;
 import net.ivoa.xml.vospace.v2.Node;
 import net.ivoa.xml.vospace.v2.View;
 import org.slf4j.Logger;
@@ -65,8 +66,11 @@ public class SetNodeController extends BaseNodeController {
 
         // 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)
+        // Also if input node is LinkNode, target must be validated as internal URI
         if (node instanceof DataNode) {
             checkViews((DataNode) node, (DataNode) toBeModifiedNode);
+        } else if(node instanceof LinkNode) {
+            this.validateInternalLinkNode((LinkNode) node);
         }
 
         //The service SHOULD throw a HTTP 500 status code including an InternalFault fault 
diff --git a/src/main/java/it/inaf/oats/vospace/UriService.java b/src/main/java/it/inaf/oats/vospace/UriService.java
index 13f755d..0f23ddb 100644
--- a/src/main/java/it/inaf/oats/vospace/UriService.java
+++ b/src/main/java/it/inaf/oats/vospace/UriService.java
@@ -32,6 +32,7 @@ import java.util.stream.Collectors;
 import javax.servlet.http.HttpServletRequest;
 import net.ivoa.xml.uws.v1.JobSummary;
 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.Protocol;
 import net.ivoa.xml.vospace.v2.Transfer;
@@ -77,7 +78,7 @@ public class UriService {
         negotiatedTransfer.setTarget(transfer.getTarget());
         negotiatedTransfer.setDirection(transfer.getDirection());
         // according to examples found in specification view is not copied
-        
+
         if (transfer.getProtocols().isEmpty()) {
             // At least one protocol is expected from client
             throw new InvalidArgumentException("Transfer contains no protocols");
@@ -118,7 +119,7 @@ public class UriService {
         }
 
         negotiatedTransfer.getProtocols().addAll(validProtocols);
-        
+
         return negotiatedTransfer;
     }
 
@@ -127,7 +128,16 @@ public class UriService {
             User user) {
         Optional<Node> optNode = nodeDao.listNode(relativePath);
         if (optNode.isPresent()) {
-            return optNode.get();
+            Node node = optNode.get();
+            if (jobType.equals(JobService.JobDirection.pullFromVoSpace)) {
+                if (node instanceof LinkNode) {
+                    if (!NodeUtils.checkIfReadable(node, user.getName(), user.getGroups())) {
+                        throw PermissionDeniedException.forPath(relativePath);
+                    }
+                    node = this.followLink((LinkNode) node);
+                }
+            }
+            return node;
         } else {
             switch (jobType) {
                 case pullFromVoSpace:
@@ -152,8 +162,8 @@ public class UriService {
         List<String> groups = user.getGroups();
 
         // Check privileges write or read according to job type
-        JobService.JobDirection jobType = 
-                JobDirection.getJobDirectionEnumFromTransfer(transfer);
+        JobService.JobDirection jobType
+                = JobDirection.getJobDirectionEnumFromTransfer(transfer);
         Node node = this.getEndpointNode(relativePath, jobType, user);
 
         switch (jobType) {
@@ -266,4 +276,11 @@ public class UriService {
 
         return (Transfer) job.getJobInfo().getAny().get(0);
     }
+
+    private Node followLink(LinkNode linkNode) {
+        String targetPath = URIUtils.returnVosPathFromNodeURI(linkNode.getTarget(), authority);
+        Optional<Node> targetNode = nodeDao.listNode(targetPath);
+        return targetNode.orElseThrow(() -> new InternalFaultException("Broken Link to target: " + targetPath));
+
+    }
 }
-- 
GitLab