Newer
Older
/*
* This file is part of vospace-rest
* Copyright (C) 2021 Istituto Nazionale di Astrofisica
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import it.inaf.ia2.aa.ServletRapClient;
import it.inaf.ia2.aa.data.User;
import it.inaf.ia2.rap.client.call.TokenExchangeRequest;
import it.inaf.oats.vospace.JobService.JobDirection;
import it.inaf.oats.vospace.datamodel.NodeProperties;
Nicola Fulvio Calabria
committed
import it.inaf.oats.vospace.datamodel.NodeUtils;
import static it.inaf.oats.vospace.datamodel.NodeUtils.urlEncodePath;
import it.inaf.oats.vospace.datamodel.Views;
import it.inaf.oats.vospace.exception.InternalFaultException;
Sonia Zorba
committed
import it.inaf.oats.vospace.exception.InvalidArgumentException;
import it.inaf.oats.vospace.exception.NodeNotFoundException;
import it.inaf.oats.vospace.exception.PermissionDeniedException;
Nicola Fulvio Calabria
committed
import it.inaf.oats.vospace.exception.ProtocolNotSupportedException;
import it.inaf.oats.vospace.exception.NodeBusyException;
import it.inaf.oats.vospace.persistence.LocationDAO;
import it.inaf.oats.vospace.persistence.NodeDAO;
import it.inaf.oats.vospace.persistence.model.Location;
import it.inaf.oats.vospace.persistence.model.LocationType;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
Nicola Fulvio Calabria
committed
import java.util.Optional;
Sonia Zorba
committed
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
Nicola Fulvio Calabria
committed
import net.ivoa.xml.vospace.v2.DataNode;
import net.ivoa.xml.vospace.v2.Protocol;
import net.ivoa.xml.vospace.v2.Transfer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@Service
public class UriService {
@Value("${vospace-authority}")
private String authority;
@Value("${file-service-url}")
private String fileServiceUrl;
@Autowired
private NodeDAO nodeDao;
Nicola Fulvio Calabria
committed
@Autowired
private LocationDAO locationDAO;
@Autowired
private HttpServletRequest servletRequest;
@Autowired
private ServletRapClient rapClient;
Nicola Fulvio Calabria
committed
@Autowired
private CreateNodeService createNodeService;
Nicola Fulvio Calabria
committed
@Autowired
private FileServiceClient fileServiceClient;
Sonia Zorba
committed
/**
Sonia Zorba
committed
* For a given job, returns a new transfer object containing only valid
* protocols (protocol negotiation) and sets proper endpoints on them.
Sonia Zorba
committed
*/
Sonia Zorba
committed
public Transfer getNegotiatedTransfer(JobSummary job, Transfer transfer) {
// Original transfer object shouldn't be modified, so a new transfer object is created
Transfer negotiatedTransfer = new Transfer();
negotiatedTransfer.setTarget(transfer.getTarget());
negotiatedTransfer.setDirection(transfer.getDirection());
// according to examples found in specification view is not copied
Sonia Zorba
committed
if (transfer.getProtocols().isEmpty()) {
// At least one protocol is expected from client
throw new InvalidArgumentException("Transfer contains no protocols");
}
JobService.JobDirection jobDirection
= JobDirection.getJobDirectionEnumFromTransfer(transfer);
Sonia Zorba
committed
List<String> validProtocolUris = new ArrayList<>();
switch (jobDirection) {
Sonia Zorba
committed
case pullFromVoSpace:
Sonia Zorba
committed
case pullToVoSpace:
Sonia Zorba
committed
validProtocolUris.add("ivo://ivoa.net/vospace/core#httpget");
break;
case pushToVoSpace:
validProtocolUris.add("ivo://ivoa.net/vospace/core#httpput");
break;
default:
throw new InternalFaultException("Unsupported job direction specified");
Sonia Zorba
committed
}
List<Protocol> validProtocols
= transfer.getProtocols().stream()
Sonia Zorba
committed
// discard invalid protocols
Sonia Zorba
committed
.filter(protocol -> validProtocolUris.contains(protocol.getUri()))
Sonia Zorba
committed
.map(p -> {
// set endpoints
Protocol protocol = new Protocol();
protocol.setUri(p.getUri());
protocol.setEndpoint(getEndpoint(job, transfer));
return protocol;
}).collect(Collectors.toList());
Sonia Zorba
committed
if (validProtocols.isEmpty()) {
Protocol protocol = transfer.getProtocols().get(0);
Nicola Fulvio Calabria
committed
throw new ProtocolNotSupportedException(protocol.getUri());
Sonia Zorba
committed
Sonia Zorba
committed
negotiatedTransfer.getProtocols().addAll(validProtocols);
return negotiatedTransfer;
Nicola Fulvio Calabria
committed
private Node getEndpointNode(String relativePath,
JobService.JobDirection jobType,
Nicola Fulvio Calabria
committed
User user) {
Optional<Node> optNode = nodeDao.listNode(relativePath);
Sonia Zorba
committed
if (optNode.isPresent()) {
Nicola Fulvio Calabria
committed
return optNode.get();
} else {
switch (jobType) {
case pullFromVoSpace:
throw new NodeNotFoundException(relativePath);
case pushToVoSpace:
case pullToVoSpace:
DataNode newNode = new DataNode();
newNode.setUri(URIUtils.returnURIFromVosPath(relativePath, authority));
return createNodeService.createNode(newNode, relativePath, user);
Nicola Fulvio Calabria
committed
default:
throw new InternalFaultException("No supported job direction specified");
}
}
}
private String getEndpoint(JobSummary job, Transfer transfer) {
boolean isArchiveView = isArchiveView(transfer);
if (!isArchiveView && transfer.getTarget().size() != 1) {
throw new InvalidArgumentException("Invalid target size: " + transfer.getTarget().size());
}
String relativePath = URIUtils.returnVosPathFromNodeURI(transfer.getTarget().get(0), authority);
Nicola Fulvio Calabria
committed
User user = (User) servletRequest.getUserPrincipal();
String creator = user.getName();
List<String> groups = user.getGroups();
// Check privileges write or read according to job type
JobService.JobDirection jobType =
JobDirection.getJobDirectionEnumFromTransfer(transfer);
Nicola Fulvio Calabria
committed
Node node = this.getEndpointNode(relativePath, jobType, user);
Nicola Fulvio Calabria
committed
switch (jobType) {
case pushToVoSpace:
case pullToVoSpace:
if (!NodeUtils.checkIfWritable(node, creator, groups)) {
throw new PermissionDeniedException(relativePath);
}
break;
case pullFromVoSpace:
if (!NodeUtils.checkIfReadable(node, creator, groups)) {
throw new PermissionDeniedException(relativePath);
}
break;
default:
Nicola Fulvio Calabria
committed
throw new InternalFaultException("No supported job direction specified");
Nicola Fulvio Calabria
committed
}
if (NodeUtils.getIsBusy(node)) {
throw new NodeBusyException(relativePath);
}
if (isArchiveView) {
return fileServiceClient.startArchiveJob(transfer, job.getJobId());
}
Location location = locationDAO.getNodeLocation(relativePath).orElse(null);
if (location != null && location.getType() == LocationType.PORTAL) {
String fileName = nodeDao.getNodeOsName(relativePath);
endpoint = "http://" + location.getSource().getHostname() + location.getSource().getBaseUrl();
if (!endpoint.endsWith("/")) {
endpoint += "/";
}
endpoint += fileName;
} else {
endpoint = fileServiceUrl + urlEncodePath(relativePath);
}
endpoint += "?jobId=" + job.getJobId();
if (!"true".equals(NodeProperties.getNodePropertyByURI(node, NodeProperties.PUBLIC_READ_URI))) {
endpoint += "&token=" + getEndpointToken(fileServiceUrl + relativePath);
}
return endpoint;
}
private boolean isArchiveView(Transfer transfer) {
if (transfer.getView() == null) {
return false;
}
String viewUri = transfer.getView().getUri();
return Views.TAR_VIEW_URI.equals(viewUri)
|| Views.ZIP_VIEW_URI.equals(viewUri);
}
private String getEndpointToken(String endpoint) {
String token = ((User) servletRequest.getUserPrincipal()).getAccessToken();
if (token == null) {
throw new PermissionDeniedException("Token is null");
}
TokenExchangeRequest exchangeRequest = new TokenExchangeRequest()
.setSubjectToken(token)
.setResource(endpoint);
// TODO: add audience and scope
return rapClient.exchangeToken(exchangeRequest, servletRequest);
}
public void setNodeRemoteLocation(String nodeUri, String contentUri) {
URL url;
try {
url = new URL(contentUri);
} catch (MalformedURLException ex) {
throw new InternalFaultException(ex);
}
Location location = locationDAO.findPortalLocation(url.getHost()).orElseThrow(()
-> new InternalFaultException("No registered location found for host " + url.getHost()));
String vosPath = URIUtils.returnVosPathFromNodeURI(nodeUri, authority);
String fileName = url.getPath().substring(url.getPath().lastIndexOf("/") + 1);
nodeDao.setNodeLocation(vosPath, location.getId(), fileName);
}
public Transfer getTransfer(JobSummary job) {
List<Object> jobPayload = job.getJobInfo().getAny();
if (jobPayload.isEmpty()) {
Sonia Zorba
committed
throw new InternalFaultException("Empty job payload for job " + job.getJobId());
}
if (jobPayload.size() > 1) {
Sonia Zorba
committed
throw new InternalFaultException("Multiple objects in job payload not supported");
}
if (!(jobPayload.get(0) instanceof Transfer)) {
Sonia Zorba
committed
throw new InternalFaultException(jobPayload.get(0).getClass().getCanonicalName()
+ " not supported as job payload. Job id: " + job.getJobId());
}
return (Transfer) job.getJobInfo().getAny().get(0);
}