Commit 06858b65 authored by Sara Bertocco's avatar Sara Bertocco
Browse files
parents 87fbb52d 3c2e7690
Loading
Loading
Loading
Loading
+13 −22
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.datamodel;

import java.util.List;
@@ -10,17 +5,11 @@ import java.util.stream.Collectors;
import net.ivoa.xml.vospace.v2.Node;
import net.ivoa.xml.vospace.v2.Property;

/**
 *
 * @author bertocco
 */
public class NodeProperties {
public abstract class NodeProperties {
    
    
    private NodeProperties() { }
    
    
    private static final String PROPERTY_BASE_URI = "ivo://ivoa.net/vospace/core#";
    public static final String AVAILABLE_SPACE_URI = "ivo://ivoa.net/vospace/core#availableSpace";      // the amount of space available within a container      
    public static final String INITIAL_CREATION_TIME_URI  = "ivo://ivoa.net/vospace/core#btime";       // the initial creation time
    public static final String CONTRIBUTOR_URI  = "ivo://ivoa.net/vospace/core#contributor"; // an entity responsible for making contributions to this resource
@@ -45,14 +34,21 @@ public class NodeProperties {
    public static final String SUBJECT_URI  = "ivo://ivoa.net/vospace/core#subject";     // the topic of the resource     
    public static final String TITLE_URI  = "ivo://ivoa.net/vospace/core#title";       // a name given to the resource  
    public static final String TYPE_URI  = "ivo://ivoa.net/vospace/core#type";        // the nature or genre of the resource
    //
    // Non-standard properties
    public static final String ASYNC_TRANS_URN = "urn:async_trans";
    public static final String STICKY_URN = "urn:sticky";

    
    public static String getStandardNodePropertyByName(Node node, String propertyName) {
        return getNodePropertyByURI(node, "ivo://ivoa.net/vospace/core#".concat(propertyName));
    }


    public static String getPropertiesStringByName(Node node, String propertyName) {
    public static String getProperty(Node node, String propertyName) {

        for (Property property : node.getProperties()) {
            if (property.getUri().equals(PROPERTY_BASE_URI.concat(propertyName))) {
            if (property.getUri().equals("ivo://ivoa.net/vospace/core#".concat(propertyName))) {
                return property.getValue();
            }
        }
@@ -60,7 +56,7 @@ public class NodeProperties {
    }
    

    public static String getPropertiesStringByURI(Node node, String uri) {
    public static String getNodePropertyByURI(Node node, String uri) {
        
        for (Property property : node.getProperties()) {
            if (uri.equals(property.getUri())) {
@@ -72,7 +68,7 @@ public class NodeProperties {
    }
    // Returns all properties stored inside the node under the requested
    // property URI.    
    public static List<String> getNodePropertiesListByURI(Node node, String propertyURI) {
    public static List<String> getNodePropertyAsListByURI(Node node, String propertyURI) {

        List<String> propertyList = node.getProperties().stream()
                .filter((i) -> i.getUri()
@@ -97,9 +93,4 @@ public class NodeProperties {

    }
    

    public static String getPropertyURI(String propertyName) {
        return PROPERTY_BASE_URI.concat(propertyName);
    }
    
}
+46 −68
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.datamodel;

import java.security.Principal;
@@ -11,30 +6,65 @@ import java.util.List;
import net.ivoa.xml.vospace.v2.ContainerNode;
import net.ivoa.xml.vospace.v2.DataNode;
import net.ivoa.xml.vospace.v2.Node;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import net.ivoa.xml.vospace.v2.StructuredDataNode;


public class NodeUtils {

    
    /**
     * Forbidden path chars are non printable characters and some symbols that
     * could create issues to scripts that manipulates files. Other UTF-8
     * characters are allowed. Front end needs to pay attention to other allowed
     * characters like & and parenthesis in any case, also to avoid XSS attacks.
     */
    private static final Pattern FORBIDDEN_CHARS = Pattern.compile("[\\x00\\x08\\x0B\\x0C\\x0E-\\x1F" + Pattern.quote("<>?\":\\|/'`*") + "]");

    /**
     * Slash is a special character in defining REST endpoints and trying to
     * define a PathVariable containing slashes doesn't work, so the endpoint
     * has been defined using "/nodes/**" instead of "/nodes/{path}" and the
     * path is extracted manually parsing the request URL.
     * path is extracted manually parsing the request URL. Proper URL encoding
     * handling is needed, considering also that slashes mustn't be escaped.
     */
    public static String getPathFromRequestURLString(String requestUrlString) {
        return getPathFromRequestURLString(requestUrlString, "/nodes/");
    }

        String[] split = requestUrlString.split("/nodes/");
    public static String getPathFromRequestURLString(String requestUrlString, String prefix) {

        String[] split = requestUrlString.split(prefix);

        String path = "/";
        if (split.length == 2) {
            path += split[1];
            String[] parts = split[1].split("/");
            path += String.join("/", Arrays.stream(parts)
                    .map(p -> {
                        String decoded = URLDecoder.decode(p, StandardCharsets.UTF_8);
                        if (FORBIDDEN_CHARS.matcher(decoded).find()) {
                            throw new IllegalArgumentException("Path segment " + decoded + " contains an illegal character");
                        }
                        return decoded;
                    })
                    .collect(Collectors.toList()));
        }
        return path;
    }

    public static String urlEncodePath(String path) {
        String[] parts = path.split("/");
        return String.join("/", Arrays.stream(parts)
                .map(p -> URLEncoder.encode(p, StandardCharsets.UTF_8).replace("+", "%20"))
                .collect(Collectors.toList()));
    }
    
    // This method assumes that URL is in the format /node1/node2/...
    // multiple slashes as a single separator are allowed
    // But the output has only single slash separators
@@ -88,6 +118,8 @@ public class NodeUtils {
        
    }
    

    
    public static boolean checkIfWritable(Node myNode, String userName, List<String> userGroups) {
        
        return checkAccessPropery(myNode, userName, userGroups, NodeProperties.GROUP_WRITE_URI);
@@ -146,58 +178,4 @@ public class NodeUtils {
        return true;
    }  
    

    public static String getDbNodeType(Node node) {
        if (node instanceof ContainerNode) {
            return "container";
        } else if (node instanceof DataNode) {
            return "data";
        }
        throw new UnsupportedOperationException("Unable to retrieve database node type for class " + node.getClass().getCanonicalName());
    }
     

    public static String getNodeName(String path) {
        String[] parsedPath = path.split("/");

        return parsedPath[parsedPath.length - 1];
    }

    
    public static String getNodeName(Node myNode) {
        String uri = myNode.getUri();
        return getNodeName(uri);
    }
        

    public static boolean getIsBusy(Node myNode) {

        if (myNode instanceof DataNode) {

            DataNode dataNode = (DataNode) myNode;
            return dataNode.isBusy();
        }

        return false;
    }
    
    
    public static Node getTypedNode(String type) {
        Node node;
        switch (type) {
            case "container":
                node = new ContainerNode();
                break;
            case "data":
                node = new DataNode();
                break;
            case "structured":
                node = new StructuredDataNode();
                break;
            default:
                throw new UnsupportedOperationException("Node type " + type + " not supported yet");
        }
        return node;
    }
    
}
+2 −1
Original line number Diff line number Diff line
@@ -8,6 +8,7 @@

package net.ivoa.xml.uws.v1;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import it.inaf.oats.vospace.datamodel.JobInfoDeserializer;
@@ -20,7 +21,6 @@ import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAnyElement;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementRef;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlSchemaType;
@@ -98,6 +98,7 @@ import org.w3c.dom.Element;
// <edit>
@XmlSeeAlso({Transfer.class}) // Necessary for setting a Transfer inside the jobInfo property.
@XmlRootElement(name = "job")
@JsonIgnoreProperties(ignoreUnknown = true)
// </edit>
public class JobSummary {

+52 −12
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.datamodel;


import java.util.ArrayList;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;


public class NodeUtilsTest {

    @Test
    public void testGetPathFromRequestURLString() {

        String requestUrl = "http://localhost/vospace/nodes/a/b/c/";
        assertEquals("/a/b/c", NodeUtils.getPathFromRequestURLString(requestUrl));
    }

    @Test
    public void testGetPathWithSpacesFromRequestURLString() {

        String requestUrl = "http://localhost/vospace/nodes/a/b/c%20d%20%C3%A4+%2B.pdf";
        assertEquals("/a/b/c d ä +.pdf", NodeUtils.getPathFromRequestURLString(requestUrl));
    }

    @Test
    public void testEncodePathSpecialChars() {

        String specialChars = "ä è#+ /other/+-ò@";
        assertEquals("%C3%A4%20%C3%A8%23%2B%20/other/%2B-%C3%B2%40", NodeUtils.urlEncodePath(specialChars));
    }

    @Test
    public void testIllegalBrakets() {
        testIllegalChars("<no>.pdf");
    }

    @Test
    public void testIllegalQuestionMark() {
        testIllegalChars("???.pdf");
    }

    @Test
    public void testIllegalQuotes() {
        testIllegalChars("\"'.pdf");
    }
    
    @Test
    public void testIllegalSlashEncoded() {
        testIllegalChars("%2F.pdf");
    }

    private void testIllegalChars(String illegalString) {
        boolean exception = false;
        try {
            NodeUtils.getPathFromRequestURLString("http://localhost/vospace/nodes/path/to/" + illegalString);
        } catch (IllegalArgumentException ex) {
            exception = true;
        }
        assertTrue(exception);
    }

    //@Test
    public void getPathFromRequestURLStringTest() {
@@ -89,5 +130,4 @@ public class NodeUtilsTest {
        assertArrayEquals(expected.toArray(), result.toArray());
        
    }
    
}
+9 −0
Original line number Diff line number Diff line
@@ -52,6 +52,15 @@ public class JobSummaryTest {
        verifyJobsAreEquals(deserialized);
    }
    
    /**
     * Uses JSON extracted from real job executed by transfer service. Contains extra field jobType.
     */
    @Test
    public void testDeserializeTransferServiceResponse() throws Exception {
        String response = "{\"jobId\": \"917c784f814c4a1a91a9d5d1af07dbe9\", \"ownerId\": \"2386\", \"jobType\": \"pullToVoSpace\", \"phase\": \"PENDING\", \"startTime\": null, \"endTime\": null, \"creationTime\": \"2021-02-03T15:05:57.233602\", \"jobInfo\": {\"transfer\": {\"view\": null, \"target\": \"vos://example.com!vospace/szorba/aaa\", \"version\": null, \"direction\": \"pullToVoSpace\", \"keepBytes\": null, \"protocols\": [{\"uri\": \"ia2:async-recall\", \"param\": [{\"uri\": \"ia2:node-type\", \"value\": \"single\"}], \"endpoint\": null}]}}, \"results\": null}";
        MAPPER.readValue(response, JobSummary.class);
    }

    private JobSummary getJobSummary() {

        JobSummary job = new JobSummary();