org.integratedmodelling.kserver.controller.GetResourceController Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of klab-server Show documentation
Show all versions of klab-server Show documentation
Spring controllers and common components for all k.LAB REST servers
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 extends ResourceService> 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