Skip to content
UriService.java 8.86 KiB
Newer Older
package it.inaf.oats.vospace;

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;
import it.inaf.oats.vospace.datamodel.NodeUtils;
import static it.inaf.oats.vospace.datamodel.NodeUtils.urlEncodePath;
import it.inaf.oats.vospace.exception.InternalFaultException;
import it.inaf.oats.vospace.exception.InvalidArgumentException;
import it.inaf.oats.vospace.exception.NodeNotFoundException;
import it.inaf.oats.vospace.exception.PermissionDeniedException;
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;
import javax.servlet.http.HttpServletRequest;
import net.ivoa.xml.uws.v1.JobSummary;
import net.ivoa.xml.uws.v1.ResultReference;
import net.ivoa.xml.vospace.v2.DataNode;
import net.ivoa.xml.vospace.v2.Node;
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;
    @Autowired
    private LocationDAO locationDAO;
    @Autowired
    private HttpServletRequest servletRequest;

    @Autowired
    private ServletRapClient rapClient;

    @Autowired
    private CreateNodeController createNodeController;

    public void setTransferJobResult(JobSummary job, Transfer transfer) {

        List<ResultReference> results = new ArrayList<>();

        ResultReference result = new ResultReference();
        result.setHref(getEndpoint(job, transfer));
        results.add(result);

        job.setResults(results);
        // Moved phase setting to caller method for ERROR management
    /**
     * Sets the endpoint value for all valid protocols (protocol negotiation).
     */
    public void setSyncTransferEndpoints(JobSummary job) {
        Transfer transfer = getTransfer(job);

        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);
            case pullFromVoSpace:
                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");
        }

        List<Protocol> validProtocols
                = transfer.getProtocols().stream()
                        .filter(protocol -> validProtocolUris.contains(protocol.getUri()))
                        .collect(Collectors.toList());
        if (validProtocols.isEmpty()) {
            Protocol protocol = transfer.getProtocols().get(0);
            throw new ProtocolNotSupportedException(protocol.getUri());

        String endpoint = getEndpoint(job, transfer);
        validProtocols.stream().forEach(p -> p.setEndpoint(endpoint));

        // Returns modified transfer containing only valid protocols
        transfer.getProtocols().clear();
        transfer.getProtocols().addAll(validProtocols);
    private Node getEndpointNode(String relativePath,
            User user) {
        Optional<Node> optNode = nodeDao.listNode(relativePath);
            return optNode.get();
        } else {
            switch (jobType) {
                case pullFromVoSpace:
                    throw new NodeNotFoundException(relativePath);
                case pushToVoSpace:
                case pullToVoSpace:
                    DataNode newNode = new DataNode();
                    newNode.setUri(relativePath);
                    return createNodeController.createNode(newNode, user);
                default:
                    throw new InternalFaultException("No supported job direction specified");
            }
        }
    }

    private String getEndpoint(JobSummary job, Transfer transfer) {

        String relativePath = transfer.getTarget().substring("vos://".length() + authority.length());

        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);
        Node node = this.getEndpointNode(relativePath, jobType, user);

        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:
                throw new InternalFaultException("No supported job direction specified");
        }

        if (NodeUtils.getIsBusy(node)) {
            throw new NodeBusyException(relativePath);
        }
        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 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 = nodeUri.replaceAll("vos://[^/]+", "");

        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()) {
            throw new InternalFaultException("Empty job payload for job " + job.getJobId());
        }
        if (jobPayload.size() > 1) {
            throw new InternalFaultException("Multiple objects in job payload not supported");
        }
        if (!(jobPayload.get(0) instanceof Transfer)) {
            throw new InternalFaultException(jobPayload.get(0).getClass().getCanonicalName()
                    + " not supported as job payload. Job id: " + job.getJobId());
        }

        return (Transfer) job.getJobInfo().getAny().get(0);
    }