Newer
Older
Sonia Zorba
committed
import it.inaf.ia2.aa.data.User;
import net.ivoa.xml.vospace.v2.Node;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.beans.factory.annotation.Autowired;
import it.inaf.oats.vospace.persistence.NodeDAO;
import java.util.List;
Nicola Fulvio Calabria
committed
import org.springframework.beans.factory.annotation.Value;
Sonia Zorba
committed
import org.springframework.web.bind.annotation.PutMapping;
Nicola Fulvio Calabria
committed
import it.inaf.oats.vospace.exception.*;
import java.util.stream.Collectors;
Sonia Zorba
committed
public class CreateNodeController extends BaseNodeController {
Sonia Zorba
committed
@Autowired
private NodeDAO nodeDao;
Nicola Fulvio Calabria
committed
@Value("${vospace-authority}")
private String authority;
Sonia Zorba
committed
@PutMapping(value = {"/nodes", "/nodes/**"},
consumes = {MediaType.APPLICATION_XML_VALUE, MediaType.APPLICATION_JSON_VALUE},
Sonia Zorba
committed
produces = {MediaType.APPLICATION_XML_VALUE, MediaType.APPLICATION_JSON_VALUE})
public Node createNode(@RequestBody Node node, User principal) {
String path = getPath();
Nicola Fulvio Calabria
committed
// Validate payload node URI
Nicola Fulvio Calabria
committed
if (!isValidURI(node.getUri())) {
Nicola Fulvio Calabria
committed
throw new InvalidURIException(node.getUri());
Nicola Fulvio Calabria
committed
}
Nicola Fulvio Calabria
committed
// Check if payload URI is consistent with http request
Nicola Fulvio Calabria
committed
if (!isUrlConsistentWithPayloadURI(node.getUri(), path)) {
Nicola Fulvio Calabria
committed
throw new InvalidURIException(node.getUri(), path);
}
Nicola Fulvio Calabria
committed
// Check if another node is already present at specified path
Nicola Fulvio Calabria
committed
// This checks if the user is trying to insert the root node at "/" too
if (nodeDao.listNode(path).isPresent()) {
throw new DuplicateNodeException(path);
}
// Retrieve parent node
Node parentNode = nodeDao.listNode(getParentPath(path))
.orElseThrow(() -> new ContainerNotFoundException(getParentPath(path)));
Nicola Fulvio Calabria
committed
// Check if parent node is not a Container node and in case throw
// appropriate exception
if (!parentNode.getType().equals("vos:ContainerNode")) {
if (parentNode.getType().equals("vos:LinkNode")) {
throw new LinkFoundException(getParentPath(path));
} else {
throw new ContainerNotFoundException(getParentPath(path));
}
Nicola Fulvio Calabria
committed
}
Nicola Fulvio Calabria
committed
// First check if parent node creator is == userid
List<String> nodeOwner
= getNodePropertyByURI(
parentNode, "ivo://ivoa.net/vospace/core#creator");
if (nodeOwner == null
|| nodeOwner.isEmpty()
|| !nodeOwner.get(0).equals(principal.getName())) {
// Node owner check has failed: let's check if user can write
// due to group privileges
Nicola Fulvio Calabria
committed
List<String> userGroups = principal.getGroups();
// If the user doesn't belong to any groups throw exception
if (userGroups == null || userGroups.isEmpty()) {
throw new PermissionDeniedException(path);
Nicola Fulvio Calabria
committed
List<String> groupWritePropValues
= getNodePropertyByURI(parentNode,
"ivo://ivoa.net/vospace/core#groupwrite");
// If groupwrite property is absent in Parent Node throw exception
if (groupWritePropValues == null
|| groupWritePropValues.isEmpty()) {
throw new PermissionDeniedException(path);
}
List<String> nodeGroups
= parsePropertyStringToList(groupWritePropValues.get(0));
if (nodeGroups.isEmpty()
|| !nodeGroups.stream()
.anyMatch((i) -> userGroups.contains(i))) {
throw new PermissionDeniedException(path);
}
}
nodeDao.createNode(node);
Sonia Zorba
committed
return node;
Nicola Fulvio Calabria
committed
Nicola Fulvio Calabria
committed
// Assuming that this service implementation uses only ! as a separator
// in the authority part of the URI
Nicola Fulvio Calabria
committed
private boolean isValidURI(String nodeURI) {
Nicola Fulvio Calabria
committed
String parsedAuthority;
Nicola Fulvio Calabria
committed
if (!nodeURI.startsWith("vos://")) {
Nicola Fulvio Calabria
committed
return false;
} else {
Nicola Fulvio Calabria
committed
parsedAuthority = nodeURI.replaceAll("vos://", "").split("/", -1)[0];
Nicola Fulvio Calabria
committed
}
Nicola Fulvio Calabria
committed
if (parsedAuthority.isEmpty()
|| !parsedAuthority.replace("~", "!").equals(authority)) {
Nicola Fulvio Calabria
committed
return false;
Nicola Fulvio Calabria
committed
}
return true;
Nicola Fulvio Calabria
committed
}
private boolean isUrlConsistentWithPayloadURI(String nodeURI, String path) {
// It's assumed that nodeURI has been validated to be in the expected
// form vos://authority[!~]somepath/mynode..."
return nodeURI.replaceAll("vos://[^/]+", "").equals(path);
}
Nicola Fulvio Calabria
committed
// 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
Nicola Fulvio Calabria
committed
private String getParentPath(String path) {
Nicola Fulvio Calabria
committed
String[] parsedPath = path.split("[/]+");
if (parsedPath.length < 2 || !parsedPath[0].isEmpty()) {
throw new IllegalArgumentException();
}
Nicola Fulvio Calabria
committed
StringBuilder sb = new StringBuilder();
Nicola Fulvio Calabria
committed
sb.append("/");
Nicola Fulvio Calabria
committed
Nicola Fulvio Calabria
committed
for (int i = 1; i < parsedPath.length - 1; i++) {
sb.append(parsedPath[i]);
if (i < parsedPath.length - 2) {
sb.append("/");
}
Nicola Fulvio Calabria
committed
}
return sb.toString();
}
Nicola Fulvio Calabria
committed
// Returns all properties stored inside the node under the requested
// property URI.
private List<String> getNodePropertyByURI(Node node, String propertyURI) {
List<String> propertyList = node.getProperties().stream()
.filter((i) -> i.getUri()
.equals(propertyURI))
.map((i) -> i.getValue())
.collect(Collectors.toList());
return propertyList;
}
Nicola Fulvio Calabria
committed
private List<String> parsePropertyStringToList(String property) {
// If separator changes, this method should remain consistent
// For now it assumes that " " is the separator
String separator = " ";
String trimmedProperty = property.trim();
if (trimmedProperty.isEmpty()) {
return List.of();
}
return List.of(trimmedProperty.split(separator));
}