All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.integratedmodelling.kserver.controller.GetResourceController Maven / Gradle / Ivy

There is a newer version: 0.9.10
Show newest version
package org.integratedmodelling.kserver.controller;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.util.Enumeration;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.IOUtils;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.methods.RequestBuilder;
import org.apache.http.entity.ContentType;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.integratedmodelling.api.configuration.IServiceConfiguration;
import org.integratedmodelling.api.data.IDataAsset;
import org.integratedmodelling.api.data.IDataService;
import org.integratedmodelling.api.modelling.IModelBean;
import org.integratedmodelling.api.network.API;
import org.integratedmodelling.api.network.INodeNetwork;
import org.integratedmodelling.common.auth.UserAuthorizationProvider;
import org.integratedmodelling.common.beans.requests.ConnectionAuthorization;
import org.integratedmodelling.common.configuration.KLAB;
import org.integratedmodelling.common.interfaces.DirectResourceService;
import org.integratedmodelling.common.interfaces.IndirectResourceService;
import org.integratedmodelling.common.interfaces.RequestParameterFilter;
import org.integratedmodelling.common.interfaces.ResourceService;
import org.integratedmodelling.common.interfaces.UrnResolver;
import org.integratedmodelling.common.resources.ResourceFactory;
import org.integratedmodelling.common.vocabulary.KlabUrn;
import org.integratedmodelling.exceptions.KlabAuthorizationException;
import org.integratedmodelling.exceptions.KlabException;
import org.integratedmodelling.exceptions.KlabIOException;
import org.integratedmodelling.exceptions.KlabInternalErrorException;
import org.integratedmodelling.exceptions.KlabUnsupportedOperationException;
import org.integratedmodelling.kserver.controller.components.UserManager;
import org.integratedmodelling.kserver.resources.services.AbstractResourceService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpHeaders;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

/**
 * Entry point for all the /get endpoints, and bridge to URN resolution and resource
 * retrieval handled by plug-in service.
 * 
 * @author ferdinando.villa
 *
 */
@RestController
public class GetResourceController extends ResourceFactory {

    @Autowired
    ApplicationContext appContext;

    @Autowired
    KServerController controller;

    @Autowired
    UserManager userManager;

    @Autowired
    UserAuthorizationProvider authorizationProvider;

    @Autowired
    UrnResolver urnResolver;

    @Autowired
    IServiceConfiguration serviceConfiguration;

    /**
     * URNs must be prefixed with the server ID in public nodes, and with "local" in
     * modeling engines. The prefix should be validated and removed before moving on with
     * access control and retrieval.
     * 
     * Authorization for this service, which is provided by both engines and nodes, is as
     * follows:
     * 
     * 1. If running in an engine, authorize using the credentials of the user owning the
     * engine - no authorization from session owners; 2. If running in a node, authorize
     * using either the key of a known node (which we have the certificate of) or a
     * previously authorized user, whose credentials have been established at
     * {@link API#IDENTIFY}.
     * 
     * When the resolver returns a URL, this implementation will only produce a proxyied
     * GET requests, even if the originating request is a POST. TODO see if that is
     * enough.
     * 
     * This is the most complex and configurable of the endpoints, with potential partial
     * match (to accommodate using this as a proxy within other services that are unaware
     * of k.LAB) and depending on matching {@link IDataAsset} and
     * {@link IServiceConfiguration} resources to define what is returned and how.
     * 
     * @param service
     * @param urn
     * @param element
     * @param headers
     * @param request
     * @param response
     * @return a model bean if that's the response, otherwise null and the response
     *         payload is handled in more direct ways.
     * @throws KlabException
     */
    @RequestMapping(
            value = { API.GET_RESOURCE, API.GET_RESOURCE + "/*" },
            method = { RequestMethod.GET, RequestMethod.POST })
    @ResponseBody
    public Object getResourceByService(@PathVariable String service, @PathVariable String urn, @RequestParam(
            value = "element",
            required = false) String element, @RequestHeader HttpHeaders headers, HttpServletRequest request, HttpServletResponse response)
                    throws KlabException {

    	KlabUrn kurn = new KlabUrn(urn);
    	
    	boolean isSelfReference = kurn.getNodeName().equals(KLAB.ENGINE.getName());
    	
        urn = validateAndRemovePrefix(urn);

        if (urn == null) {
            return null;
        }

        Class servclass = serviceRegistry.get(service);
        if (servclass == null) {
            throw new KlabUnsupportedOperationException("resource access service " + service
                    + " not found in registry");
        }

        /*
         * FIXME this is called with NODE authentication or nothing. It can match either a
         * previously authorized user of this node (see CONNECT) or the local cert key if
         * this comes from another node.
         */
        boolean authenticated = false;
        ConnectionAuthorization authorization = null;
        List auth = headers.get(API.AUTHENTICATION_HEADER);
        String token = null;
        if (auth != null && auth.size() > 0) {
            token = auth.get(0);
        }
        if (token != null) {

            if (KLAB.ENGINE.getNetwork() instanceof INodeNetwork) {
                authenticated = token.equals(((INodeNetwork) KLAB.ENGINE.getNetwork()).getKey());
            }
            if (!authenticated) {
                /*
                 * check if it's previously authorized. Works for nodes and for engines.
                 */
                authorization = authorizationProvider.getAuthorization(token);
                if (authorization != null) {
                    authorization.setRequestingIP(request.getRemoteAddr());
                    authenticated = true;
                }
            }
        }

        try {

            ResourceService serv = servclass.newInstance();

            if (serv == null) {
                // unlikely indeed.
                throw new KlabUnsupportedOperationException("resource service " + service
                        + " could not be created");
            }

            if (serv instanceof AbstractResourceService) {
                ((AbstractResourceService) serv).setApplicationContext(appContext);
            }

            Object o = null;
            IDataAsset as = null;
            IDataService dataService = null;

            if (serv instanceof DirectResourceService) {
                o = ((DirectResourceService) serv)
                        .resolveUrn(urn, element, authorization, KLAB.ENGINE
                                .getResourceConfiguration(), request
                                        .getParameterMap());
            } else if (serv instanceof IndirectResourceService) {

                /**
                 * Filter special URNs for access to service without a data record. If the
                 * service key matches, authentication is ensured.
                 */
                if (urn.startsWith("service:")) {

                    String serviceKey = kurn.getResourceId();
                    dataService = serviceConfiguration.getDataService(serviceKey);
                    if (dataService != null && !dataService.getType().equals(kurn.getNamespace())) {
                        dataService = null;
                    }
                    if (dataService == null) {
                        throw new KlabAuthorizationException("service access denied because of malformed authorization in URN");
                    }
                    authenticated = true;

                } else {
                    as = urnResolver
                            .resolveUrn(getUrnPrefix() + urn, authorization, KLAB.ENGINE
                                    .getResourceConfiguration());
                    if (as != null) {
                        dataService = as.getServiceKey() == null ? null
                                : serviceConfiguration.getDataService(as.getServiceKey());
                    }
                }

                if (as != null || dataService != null) {
                    o = ((IndirectResourceService) serv)
                            .resolveUrn(as, dataService, authorization, KLAB.ENGINE
                                    .getResourceConfiguration(), request
                                            .getParameterMap());
                }
            }

            /*
             * requests from URNs hosted on self always go through. Without this, projects
             * with URNs that are resolved locally won't load.
             */
            if (!authenticated && !isSelfReference && !serv.allowsUnauthenticated(urn)) {
                throw new KlabAuthorizationException("service " + service
                        + " does not allow unauthenticated access: "
                        + (token == null ? "no credentials in request" : "invalid credentials in request")
                        + " for URN " + urn);
            }

            if (o instanceof IModelBean) {
                return o;
            } else if (o instanceof File) {
                response.setContentType(URLConnection.guessContentTypeFromName(((File) o).getName()));
                try (InputStream in = new FileInputStream((File) o)) {
                    IOUtils.copy(in, response.getOutputStream());
                } catch (IOException e) {
                    throw new KlabIOException(e);
                }
            } else if (o instanceof URL || o instanceof URI) {
                HttpUriRequest proxiedRequest = createHttpUriRequest(o instanceof URI ? (URI) o
                        : ((URL) o).toURI(), request, serv, as);
                try (CloseableHttpClient client = HttpClients.createDefault();
                        CloseableHttpResponse proxiedResponse = client.execute(proxiedRequest)) {
                    writeToResponse(proxiedResponse, response);
                } catch (Exception e) {
                    throw new KlabIOException(e);
                }
            } else if (o instanceof InputStream) {
                response.setContentType(ContentType.DEFAULT_BINARY.toString());
                try {
                    IOUtils.copy((InputStream) o, response.getOutputStream());
                    ((InputStream) o).close();
                } catch (IOException e) {
                    throw new KlabIOException(e);
                }
            } else {
                throw new KlabUnsupportedOperationException("could not understand " + service
                        + " service response for URN " + urn);
            }
        } catch (Exception e) {
            throw new KlabInternalErrorException(e);
        }

        return null;
    }

    /**
     * Check the URN prefix and ensure it's OK for this server. If not, return null, which
     * will cause a null response meaning "I don't serve this". Pass the "klab:" prefix
     * unmodified as that tags "system" URNs that work everywhere.
     * 
     * @param urn
     * @return
     */
    protected String validateAndRemovePrefix(String urn) {

        if (urn.startsWith("klab:")) {
            return urn;
        }
        String prefix = getUrnPrefix();
        if (!urn.startsWith(prefix)) {
            return null;
        }
        return urn.substring(prefix.length());
    }

    public static String getUrnPrefix() {
        return KLAB.ENGINE.getName() + ":";
    }

    private void writeToResponse(HttpResponse proxiedResponse, HttpServletResponse response)
            throws KlabIOException {
        for (Header header : proxiedResponse.getAllHeaders()) {
            if ((!header.getName().equals("Transfer-Encoding")) || (!header.getValue().equals("chunked"))) {
                response.addHeader(header.getName(), header.getValue());
            }
        }
        try (InputStream is = proxiedResponse.getEntity().getContent();
                OutputStream os = response.getOutputStream()) {
            IOUtils.copy(is, os);
        } catch (Exception e) {
            throw new KlabIOException(e);
        }
    }

    private HttpUriRequest createHttpUriRequest(URI uri, HttpServletRequest request, ResourceService service, IDataAsset resource)
            throws URISyntaxException {

        RequestBuilder rb = RequestBuilder.create(request.getMethod());
        rb.setUri(uri);

        Enumeration headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String headerName = headerNames.nextElement();
            String headerValue = request.getHeader(headerName);
            if (service instanceof RequestParameterFilter) {
                headerValue = ((RequestParameterFilter) service)
                        .filterHeader(headerName, headerValue, resource);
            }
            if (headerValue != null) {
                rb.addHeader(headerName, headerValue);
            }
        }

        /*
         * add any parameters from request
         */
        for (String key : request.getParameterMap().keySet()) {
            String value = request.getParameter(key);
            if (service instanceof RequestParameterFilter) {
                value = ((RequestParameterFilter) service).filter(key, value, resource);
            }
            if (value != null) {
                rb.addParameter(key, value);
            }
        }

        HttpUriRequest proxiedRequest = rb.build();
        
        KLAB.info("redirecting to URI " + proxiedRequest.getURI().toString());
        
        return proxiedRequest;
    }

    // @Override
    // public void afterPropertiesSet() throws Exception {
    //
    // if (urnResolver instanceof MapUrnResolver) {
    // ((MapUrnResolver) urnResolver).initialize();
    // }
    //
    // if (serviceConfiguration instanceof MapServiceConfiguration) {
    // ((MapServiceConfiguration) serviceConfiguration).initialize();
    // }
    //
    // }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy