Skip to content

Commit

Permalink
Add /api/arp/cedarResourceProxy endpoint
Browse files Browse the repository at this point in the history
This endpoint can be configured in AROMA as

VITE_REACT_APP_CEDAR_PROXY=http://localhost:8080/api/arp/cedarResourceProxy/

If configured CEDAR schemas will be downloaded via this endpoint, which
makes sure schemas can only be read and only makes it unnecesary for
AROMA to provide CEDAR api key as this endpoint adds the necessary
authentication to the proxied request.
  • Loading branch information
beepsoft committed Feb 27, 2024
1 parent a9579a6 commit 515e03b
Show file tree
Hide file tree
Showing 2 changed files with 112 additions and 0 deletions.
110 changes: 110 additions & 0 deletions src/main/java/edu/harvard/iq/dataverse/api/arp/ArpApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
Expand All @@ -43,6 +47,19 @@
import static jakarta.ws.rs.core.Response.Status.*;
import static edu.harvard.iq.dataverse.api.ApiConstants.STATUS_ERROR;

import java.net.URI;
import jakarta.ws.rs.core.HttpHeaders;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.List;
import java.util.Map;


@Path("arp")
public class ArpApi extends AbstractApiBean {

Expand Down Expand Up @@ -91,6 +108,33 @@ public class ArpApi extends AbstractApiBean {
@Inject
DataverseSession dataverseSession;

HttpClient proxyClient;

public ArpApi() throws NoSuchAlgorithmException, KeyManagementException
{
// Configure proxyClient to ignore SSL Certs
TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[]{};
}
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
}
};

// Install the all-trusting trust manager
SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, trustAllCerts, new SecureRandom());

// Create a HttpClient with the custom SSLContext
proxyClient = HttpClient.newBuilder()
.sslContext(sslContext)
.build();
}

/**
* Checks whether a CEDAR template is valid for use as a Metadatablock.
*
Expand Down Expand Up @@ -830,4 +874,70 @@ public Response updateRoCrate(
}
}

/**
* Proxies requests to the cedar resource server for reading public schemas. Since we cannot have a CEDAR user which
* only has read right, therefore we cannot have the api key in AROMA, otherwise malicious users may make modifications
* in the name of that user. Instead we have this poxy, which aonly allows GET access to schemas and also
* adds the neccesary api key authentication to request. For this to work the arp.cedar.domain and
* arp.cedar.proxyApiKey configurations must be set.
*
* @param urlInPath the URL to download
* @param incomingHeaders the request headers
*
* @return contents of urlInPath
*/
@GET
@Path("/cedarResourceProxy/{url}")
@Produces(MediaType.APPLICATION_JSON)
public Response cedarResourceProxy(
@PathParam("url") String urlInPath,
@Context HttpHeaders incomingHeaders
) {
String subdomain = "resource."+arpConfig.get("arp.cedar.domain");
String apiKey = arpConfig.get("arp.cedar.proxyApiKey");

if (urlInPath == null || urlInPath.isBlank()) {
return Response.status(Response.Status.BAD_REQUEST).entity("URL parameter is required").build();
}

try {
URI uri = new URI(urlInPath);
if (!uri.getHost().endsWith(subdomain)) {
return Response.status(Response.Status.BAD_REQUEST).entity("Invalid URL").build();
}

HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
.uri(uri)
.GET();

// Set authorization and accept headers explicitly
requestBuilder.header("Authorization", "apiKey " + apiKey);
requestBuilder.header("Accept", "application/json");

// Forward the request
HttpResponse<byte[]> proxiedResponse = proxyClient.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofByteArray());

// Begin building the response to the client
Response.ResponseBuilder responseBuilder = Response.ok(proxiedResponse.body());

// Filter and set response headers
java.net.http.HttpHeaders responseHeaders = proxiedResponse.headers();
responseHeaders.map().forEach((key, values) -> {
if (!List.of("Connection", "Keep-Alive", "Proxy-Authenticate", "Proxy-Authorization", "TE", "Trailer", "Transfer-Encoding", "Upgrade", "Content-Encoding", "Content-Length").contains(key)) {
responseBuilder.header(key, String.join(",", values));
}
});

// Add CORS headers to the response
responseBuilder.header("Access-Control-Allow-Origin", "*")
.header("Access-Control-Allow-Methods", "GET")
.header("Access-Control-Allow-Headers", "*");

return responseBuilder.build();
} catch (Exception e) {
e.printStackTrace();
return Response.serverError().entity("Failed to proxy request: " + e.getMessage()).build();
}
}

}
2 changes: 2 additions & 0 deletions src/main/resources/arp/default.properties
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,6 @@ arp.w3id.base=https://w3id.org/arp/localdev

# CEDAR services base domain
arp.cedar.domain=arp.orgx
arp.cedar.proxyApiKey=0000111122223333444455556666777788889999aaaabbbbccccddddeeeeffff

terminology.url.template=https://terminology.arp.orgx/bioportal/ontologies/%s/classes/%s/descendants?pageSize=500

0 comments on commit 515e03b

Please sign in to comment.