Commit 582b968d authored by Sonia Zorba's avatar Sonia Zorba
Browse files

Handled token for private files retrieval. Added several tests

parent 73b81e41
Loading
Loading
Loading
Loading
+26 −1
Original line number Original line Diff line number Diff line
@@ -2,6 +2,31 @@


## Database
## Database


This VOSpace implementation uses the database populated by the [VOSpace Transfer Service application](https://www.ict.inaf.it/gitlab/ia2/vospace-transfer-service). To avoid duplicating database definitions, DAO test classes load the database directly from the files of that repository. We assume that when running the tests the git repository exists and it is located in the same parent folder containing this repository. We could decide to create a dedicate common repository for sharing only the database structure and configuration files between the 2 projects.
This VOSpace implementation uses the database populated by the [VOSpace Transfer Service application](https://www.ict.inaf.it/gitlab/ia2/vospace-transfer-service). The structure of the database is defined in a [separate repository](https://www.ict.inaf.it/gitlab/ia2/vospace-file-catalog). To avoid duplicating database definitions, DAO test classes load the database directly from the files of that repository. We assume that when running the tests the git repository exists and it is located in the same parent folder containing this repository.


To reconfigure the path of that repository edit the property `init_database_scripts_path` in test.properties.
To reconfigure the path of that repository edit the property `init_database_scripts_path` in test.properties.

## Loading fake users in MockMvc

Test classes annotated with `@SpringBootTest` and `@AutoConfigureMockMvc` can be used to test REST controllers. Theoretically it should be possible configure a fake principal to each test request using the following notation:

```java
mockMvc.perform(post("/endpoint").principal(myFakeUser));
```

However it seems that the method is ignored if the principal is set using a custom servlet filter, like in our case (see `TokenFilter` registration defined in `VospaceApplication` class).

To bypass the problem a fake `TokenFilter` has been defined in `TokenFilterConfig` test class. This filter returns some fake users based on the received fake token. If you need additional test users just add them in the `getFakeUser()` method.

To use the fake filter add the following annotations to the test class:

```java
@ContextConfiguration(classes = {TokenFilterConfig.class})
@TestPropertySource(properties = "spring.main.allow-bean-definition-overriding=true")
```

Then add the fake token to the test request:

```java
mockMvc.perform(post("/endpoint").header("Authorization", "Bearer user1_token"));
```
+2 −0
Original line number Original line Diff line number Diff line
@@ -76,6 +76,8 @@ public class TransferController {
                return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
                return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
            }
            }


            jobService.setJobPhase(job, phase);

            return getJobRedirect(job.getJobId());
            return getJobRedirect(job.getJobId());


        }).orElse(ResponseEntity.notFound().build());
        }).orElse(ResponseEntity.notFound().build());
+50 −5
Original line number Original line Diff line number Diff line
package it.inaf.oats.vospace;
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.persistence.NodeDAO;
import it.inaf.oats.vospace.persistence.NodeDAO;
import java.util.ArrayList;
import java.util.ArrayList;
import java.util.List;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import net.ivoa.xml.uws.v1.JobSummary;
import net.ivoa.xml.uws.v1.JobSummary;
import net.ivoa.xml.uws.v1.ResultReference;
import net.ivoa.xml.uws.v1.ResultReference;
import net.ivoa.xml.vospace.v2.Node;
import net.ivoa.xml.vospace.v2.Node;
import net.ivoa.xml.vospace.v2.Property;
import net.ivoa.xml.vospace.v2.Protocol;
import net.ivoa.xml.vospace.v2.Protocol;
import net.ivoa.xml.vospace.v2.Transfer;
import net.ivoa.xml.vospace.v2.Transfer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Autowired;
@@ -24,6 +29,12 @@ public class UriService {
    @Autowired
    @Autowired
    private NodeDAO nodeDao;
    private NodeDAO nodeDao;


    @Autowired
    private HttpServletRequest servletRequest;

    @Autowired
    private ServletRapClient rapClient;

    public void setTransferJobResult(JobSummary job) {
    public void setTransferJobResult(JobSummary job) {


        List<ResultReference> results = new ArrayList<>();
        List<ResultReference> results = new ArrayList<>();
@@ -39,11 +50,12 @@ public class UriService {


        Transfer transfer = getTransfer(job);
        Transfer transfer = getTransfer(job);


        Protocol protocol = new Protocol();
        Protocol protocol = transfer.getProtocols().get(0);
        protocol.setUri("ivo://ivoa.net/vospace/core#httpget");
        protocol.setEndpoint(getEndpoint(job));


        transfer.getProtocols().add(protocol);
        if (!"ivo://ivoa.net/vospace/core#httpget".equals(protocol.getUri())) {
            throw new IllegalStateException("Unsupported protocol " + protocol.getUri());
        }
        protocol.setEndpoint(getEndpoint(job));
    }
    }


    private String getEndpoint(JobSummary job) {
    private String getEndpoint(JobSummary job) {
@@ -58,7 +70,40 @@ public class UriService {
        // TODO build the path according to node type
        // TODO build the path according to node type
        //
        //
        // TODO add token for authenticated access
        // TODO add token for authenticated access
        return fileServiceUrl + relativePath + "?jobId=" + job.getJobId();
        String endpoint = fileServiceUrl + relativePath + "?jobId=" + job.getJobId();

        if (!"true".equals(getProperty(node, "publicread"))) {
            endpoint += "&token=" + getEndpointToken(fileServiceUrl + relativePath);
        }

        return endpoint;
    }

    private String getEndpointToken(String endpoint) {

        String token = ((User) servletRequest.getUserPrincipal()).getAccessToken();

        if (token == null) {
            // TODO: use PermissionDenied VoSpaceException
            throw new IllegalStateException("Token is null");
        }

        TokenExchangeRequest exchangeRequest = new TokenExchangeRequest()
                .setSubjectToken(token)
                .setResource(endpoint);

        // TODO: add audience and scope
        return rapClient.exchangeToken(exchangeRequest, servletRequest);
    }

    private String getProperty(Node node, String propertyName) {

        for (Property property : node.getProperties()) {
            if (property.getUri().equals("ivo://ivoa.net/vospace/core#".concat(propertyName))) {
                return property.getValue();
            }
        }
        return null;
    }
    }


    private Transfer getTransfer(JobSummary job) {
    private Transfer getTransfer(JobSummary job) {
+7 −0
Original line number Original line Diff line number Diff line
package it.inaf.oats.vospace;
package it.inaf.oats.vospace;


import it.inaf.ia2.aa.ServiceLocator;
import it.inaf.ia2.aa.ServletRapClient;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
@@ -20,4 +22,9 @@ public class VospaceApplication {
        registration.addUrlPatterns("/*");
        registration.addUrlPatterns("/*");
        return registration;
        return registration;
    }
    }

    @Bean
    public ServletRapClient servletRapClient() {
        return (ServletRapClient) ServiceLocator.getInstance().getRapClient();
    }
}
}
+98 −0
Original line number Original line Diff line number Diff line
package it.inaf.oats.vospace;

import it.inaf.ia2.aa.TokenFilter;
import it.inaf.ia2.aa.data.User;
import java.io.IOException;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Arrays;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;

@TestConfiguration
public class TokenFilterConfig {

    @Bean
    @Primary
    public FilterRegistrationBean tokenFilterRegistration() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new FakeTokenFilter());
        registration.addUrlPatterns("/*");
        return registration;
    }

    private static class FakeTokenFilter extends TokenFilter {

        @Override
        public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {

            HttpServletRequest request = (HttpServletRequest) req;
            HttpServletResponse response = (HttpServletResponse) res;

            String authHeader = request.getHeader("Authorization");

            if (authHeader != null) {
                if (authHeader.startsWith("Bearer ")) {
                    String token = authHeader.substring("Bearer ".length());
                    HttpServletRequestWrapper requestWithPrincipal = new RequestWithPrincipal(request, getFakeUser(token));
                    chain.doFilter(requestWithPrincipal, response);
                    return;
                }
            }

            chain.doFilter(getAnonymousServletRequest(request), response);
        }

        private User getFakeUser(String token) {

            User user = new User();

            switch (token) {
                case "user1_token":
                    user.setUserId("user1").setUserLabel("User1");
                    break;
                case "user2_token":
                    user.setUserId("user2").setUserLabel("User2").setGroups(Arrays.asList("group1", "group2"));
                    break;
                default:
                    throw new IllegalArgumentException("Fake user not configured for token " + token);
            }

            user.setAccessToken(token);

            return user;
        }

        private static HttpServletRequestWrapper getAnonymousServletRequest(HttpServletRequest request) {
            User anonymousUser = new User()
                    .setUserId("anonymous")
                    .setUserLabel("Anonymous")
                    .setGroups(new ArrayList<>());
            return new RequestWithPrincipal(request, anonymousUser);
        }

        private static class RequestWithPrincipal extends HttpServletRequestWrapper {

            private final User user;

            public RequestWithPrincipal(HttpServletRequest request, User user) {
                super(request);
                this.user = user;
            }

            @Override
            public Principal getUserPrincipal() {
                return user;
            }
        }
    }
}
Loading