Commit 06858b65 authored by Sara Bertocco's avatar Sara Bertocco
Browse files
parents 87fbb52d 3c2e7690
/*
* 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()
......@@ -96,10 +92,5 @@ public class NodeProperties {
return List.of(trimmedProperty.split(separator));
}
public static String getPropertyURI(String propertyName) {
return PROPERTY_BASE_URI.concat(propertyName);
}
}
/*
* 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,29 +6,64 @@ 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 ) {
String[] split = requestUrlString.split("/nodes/");
public static String getPathFromRequestURLString(String requestUrlString) {
return getPathFromRequestURLString(requestUrlString, "/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
......@@ -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);
......@@ -144,60 +176,6 @@ 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;
}
}
}
......@@ -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 {
......
/*
* 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());
}
}
......@@ -51,6 +51,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() {
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment