Commit 83799fd3 authored by Nicola Fulvio Calabria's avatar Nicola Fulvio Calabria
Browse files

Added URI validation utility class

parent da5f12df
Loading
Loading
Loading
Loading
+6 −33
Original line number Original line Diff line number Diff line
@@ -39,14 +39,12 @@ public class CreateNodeService {
        
        
        LOG.debug("createNodeService called for node with URI {} and PATH {}", node.getUri(), path);
        LOG.debug("createNodeService called for node with URI {} and PATH {}", node.getUri(), path);
        
        
        // Validate payload node URI
        // Get Node path (and validates it too)
        if (!isValidURI(node.getUri())) {
        String decodedURIFromNode = URIUtils.returnVosPathFromNodeURI(node.getUri(), authority);
            throw new InvalidURIException(node.getUri());
        }


        // Check if payload URI is consistent with http request
        // Check if payload URI is consistent with http request
        if (!isUrlConsistentWithPayloadURI(node.getUri(), path)) {
        if (!decodedURIFromNode.equals(path)) {
            throw new InvalidURIException(node.getUri(), path);
            throw new InvalidURIException(decodedURIFromNode, path);
        }
        }


        // Check if another node is already present at specified path
        // Check if another node is already present at specified path
@@ -127,31 +125,6 @@ public class CreateNodeService {


    }    
    }    
        
        
    // Assuming that this service implementation uses only ! as a separator
    // in the authority part of the URI
    private boolean isValidURI(String nodeURI) {
        String parsedAuthority;
        if (!nodeURI.startsWith("vos://")) {
            return false;
        } else {
            parsedAuthority = nodeURI.replaceAll("vos://", "").split("/", -1)[0];
        }

        if (parsedAuthority.isEmpty()
                || !parsedAuthority.replace("~", "!").equals(authority)) {
            return false;
        }

        return true;
    }

    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);
    }
    
    private String getParentPath(String path) {
    private String getParentPath(String path) {
        return NodeUtils.getParentPath(path);
        return NodeUtils.getParentPath(path);
    }    
    }    
+5 −3
Original line number Original line Diff line number Diff line
@@ -8,6 +8,7 @@ package it.inaf.oats.vospace;
import it.inaf.ia2.aa.data.User;
import it.inaf.ia2.aa.data.User;
import it.inaf.oats.vospace.datamodel.NodeUtils;
import it.inaf.oats.vospace.datamodel.NodeUtils;
import it.inaf.oats.vospace.exception.InternalFaultException;
import it.inaf.oats.vospace.exception.InternalFaultException;
import it.inaf.oats.vospace.exception.InvalidURIException;
import it.inaf.oats.vospace.exception.NodeBusyException;
import it.inaf.oats.vospace.exception.NodeBusyException;
import it.inaf.oats.vospace.exception.NodeNotFoundException;
import it.inaf.oats.vospace.exception.NodeNotFoundException;
import it.inaf.oats.vospace.exception.PermissionDeniedException;
import it.inaf.oats.vospace.exception.PermissionDeniedException;
@@ -41,10 +42,10 @@ public class MoveService {
    public void processMoveJob(Transfer transfer) {
    public void processMoveJob(Transfer transfer) {


        // Get Source Vos Path
        // Get Source Vos Path
        String sourcePath = transfer.getTarget().substring("vos://".length() + authority.length());
        String sourcePath = URIUtils.returnVosPathFromNodeURI(transfer.getTarget(), authority);


        // Get Destination Vos Path (it's in transfer direction)
        // Get Destination Vos Path (it's in transfer direction)
        String destinationPath = transfer.getDirection().substring("vos://".length() + authority.length());
        String destinationPath = URIUtils.returnVosPathFromNodeURI(transfer.getDirection(), authority);


        // Extract User permissions from servlet request
        // Extract User permissions from servlet request
        User user = (User) servletRequest.getUserPrincipal();
        User user = (User) servletRequest.getUserPrincipal();
@@ -114,6 +115,7 @@ public class MoveService {
        }
        }
    }
    }


    
    private void validatePath(String path) {        
    private void validatePath(String path) {        
        if (path.equals("/")) {            
        if (path.equals("/")) {            
            throw new IllegalArgumentException("Cannot move root node or to root node");
            throw new IllegalArgumentException("Cannot move root node or to root node");
+66 −0
Original line number Original line Diff line number Diff line
/*
 * 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.oats.vospace.exception.InvalidArgumentException;
import it.inaf.oats.vospace.exception.InvalidURIException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.regex.Pattern;

public class URIUtils {

    // It's different from the one in NodeUtils!
    // Slashes are treated separately
    private static final Pattern FORBIDDEN_CHARS = Pattern.compile("[\\x00\\x08\\x0B\\x0C\\x0E-\\x1F" + Pattern.quote("<>?\":\\|'`*") + "]");

    // This method validates the URI too
    public static String returnVosPathFromNodeURI(String nodeURI, String authority) {
        
        String scheme = "vos";
        String resultPath = null;
        
        try{
        URI uri = new URI(nodeURI);
        
        // Check scheme
        if(!uri.isAbsolute() || 
                uri.isOpaque() || 
                !uri.getRawSchemeSpecificPart().startsWith("//") ||
                !uri.getScheme().equalsIgnoreCase(scheme))
            throw new InvalidURIException(nodeURI);
        
        // Check authority
        if(!uri.getAuthority().replace("~", "!").equals(authority))
            throw new InvalidURIException(nodeURI);
        
        // Check path
        String rawPath = uri.getRawPath();
        
        // Check if raw Path is null or contains percent encoded slashes or multiple
        // separators
        if(rawPath == null ||                 
                rawPath.contains("//") ||
                rawPath.contains("%2F") || 
                rawPath.contains("%2f"))
            throw new InvalidURIException(nodeURI);
        
        resultPath = uri.getPath();
        
        if(resultPath.isBlank() || 
                FORBIDDEN_CHARS.matcher(resultPath).find() ||
                (!resultPath.equals("/") && resultPath.endsWith("/")))
            throw new InvalidURIException(nodeURI);        
        
        } catch(URISyntaxException e) {
            throw new InvalidURIException(nodeURI);
        }
        
        return resultPath;      

    }

}
+82 −0
Original line number Original line Diff line number Diff line
/*
 * This file is part of vospace-datamodel
 * Copyright (C) 2021 Istituto Nazionale di Astrofisica
 * SPDX-License-Identifier: GPL-3.0-or-later
 */
package it.inaf.oats.vospace;

import it.inaf.oats.vospace.exception.InvalidURIException;
import java.util.ArrayList;
import java.util.List;
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.Property;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;

public class URIUtilsTest {
    
    private final String authority = "example.com!vospace";
    
    @Test
    public void testReturnVosPathFromNodeURI() {
        
        String test1 = "vos://example.com!wrong/";
        
        assertThrows(InvalidURIException.class, () -> {
            URIUtils.returnVosPathFromNodeURI(test1, authority);
        });
        
        String test2 = "wrong://example.com!wrong/";
        
        assertThrows(InvalidURIException.class, () -> {
            URIUtils.returnVosPathFromNodeURI(test2, authority);
        });
        
        String test3 = "vos://example.com!vospace";
        
        assertThrows(InvalidURIException.class, () -> {
            URIUtils.returnVosPathFromNodeURI(test3, authority);
        });
        
        String test4 = "vos://example.com!vospace/n1/n2/n3/n%2F4";
        
        assertThrows(InvalidURIException.class, () -> {
            URIUtils.returnVosPathFromNodeURI(test4, authority);
        });
        
        String test5 = "vos://example.com!vospace/n1/n2/n3//n4";
        
        assertThrows(InvalidURIException.class, () -> {
            URIUtils.returnVosPathFromNodeURI(test5, authority);
        });
        
        String test6 = "vos://example.com!vospace/n1/n2/n3/n4/";
        
        assertThrows(InvalidURIException.class, () -> {
            URIUtils.returnVosPathFromNodeURI(test6, authority);
        });
        
        String test7 = "vos://example.com!vospace/n1/n2/n*3/n4/";
        
        assertThrows(InvalidURIException.class, () -> {
            URIUtils.returnVosPathFromNodeURI(test7, authority);
        });
        
        String test8 = "vos://example.com!vospace/n1/n2/n3/n4";
        
        assertEquals("/n1/n2/n3/n4", 
                URIUtils.returnVosPathFromNodeURI(test8, authority));
        
        
        
        
    }

    
}