/* * This file is part of vospace-rest * Copyright (C) 2021 Istituto Nazionale di Astrofisica * SPDX-License-Identifier: GPL-3.0-or-later */ 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.NodeNotFoundException; 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.LinkNode; 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.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestParam; 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; @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}) public ResponseEntity setNode(@RequestBody Node node, User principal, HttpServletRequest request, @RequestParam(value = "recursive", required = false, defaultValue = "false") boolean recursive) { String path = getPath(); LOG.debug("setNode called for path {}", path); // Validate and check payload node URI consistence with request this.validateAndCheckPayloadURIConsistence(node); //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)); // 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())) { throw PermissionDeniedException.forPath(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)) { LOG.debug("setNode trying to modify type. Stored ", storedNodeType + ", requested " + newNodeType); throw PermissionDeniedException.forPath(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) // 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 // 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, recursive)); } private void checkViews(DataNode requestedDataNode, DataNode savedDataNode) { checkAcceptsView(requestedDataNode, savedDataNode); checkProvidesView(requestedDataNode, savedDataNode); } private void checkAcceptsView(DataNode requestedDataNode, DataNode savedDataNode) { List requestedAcceptedViews = requestedDataNode.getAccepts(); List 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"); } } private void checkProvidesView(DataNode requestedDataNode, DataNode savedDataNode) { List requestedProvidedViews = requestedDataNode.getProvides(); List 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 viewsA, List 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; } }