From 01bbec367b2a82a871d21669b71cf6e170d18ae7 Mon Sep 17 00:00:00 2001 From: Richard Eckart de Castilho Date: Fri, 10 Nov 2023 09:03:50 +0100 Subject: [PATCH] #4289 - Ability to skip SSL certificate validation on external recommenders - Added checkbox in external recommender settings that can be used to disable certificate checks --- .../imls/external/v1/ExternalRecommender.java | 94 ++++++++++++++++--- .../v1/ExternalRecommenderTraits.java | 11 +++ .../v1/ExternalRecommenderTraitsEditor.html | 10 ++ .../v1/ExternalRecommenderTraitsEditor.java | 8 +- ...ExternalRecommenderTraitsEditor.properties | 1 + 5 files changed, 107 insertions(+), 17 deletions(-) diff --git a/inception/inception-imls-external/src/main/java/de/tudarmstadt/ukp/inception/recommendation/imls/external/v1/ExternalRecommender.java b/inception/inception-imls-external/src/main/java/de/tudarmstadt/ukp/inception/recommendation/imls/external/v1/ExternalRecommender.java index 8d553e8d731..d3f4f8edbfe 100644 --- a/inception/inception-imls-external/src/main/java/de/tudarmstadt/ukp/inception/recommendation/imls/external/v1/ExternalRecommender.java +++ b/inception/inception-imls-external/src/main/java/de/tudarmstadt/ukp/inception/recommendation/imls/external/v1/ExternalRecommender.java @@ -29,15 +29,24 @@ import java.io.IOException; import java.io.InputStream; import java.io.StringWriter; +import java.lang.invoke.MethodHandles; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpRequest.BodyPublishers; import java.net.http.HttpResponse; import java.net.http.HttpResponse.BodyHandlers; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.List; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + import org.apache.commons.io.IOUtils; import org.apache.uima.cas.CAS; import org.apache.uima.cas.CASException; @@ -80,14 +89,15 @@ public class ExternalRecommender { public static final Key KEY_TRAINING_COMPLETE = new Key<>("training_complete"); - private static final Logger LOG = LoggerFactory.getLogger(ExternalRecommender.class); + private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); private static final int HTTP_TOO_MANY_REQUESTS = 429; private static final int HTTP_BAD_REQUEST = 400; private final ExternalRecommenderProperties properties; private final ExternalRecommenderTraits traits; - private final HttpClient client; + + private HttpClient _client; public ExternalRecommender(ExternalRecommenderProperties aProperties, Recommender aRecommender, ExternalRecommenderTraits aTraits) @@ -96,7 +106,56 @@ public ExternalRecommender(ExternalRecommenderProperties aProperties, Recommende properties = aProperties; traits = aTraits; - client = HttpClient.newBuilder().connectTimeout(properties.getConnectTimeout()).build(); + } + + private HttpClient getClient() throws RecommendationException + { + try { + if (_client == null) { + var clientBuilder = HttpClient.newBuilder() // + .connectTimeout(properties.getConnectTimeout()); + if (!traits.isVerifyCertificates()) { + var sslContext = makeNonVerifyingSslContext(); + + clientBuilder.sslContext(sslContext); + + } + _client = clientBuilder.build(); + } + return _client; + } + catch (KeyManagementException | NoSuchAlgorithmException e) { + throw new RecommendationException("Unable to initialize HTTP client", e); + } + } + + private SSLContext makeNonVerifyingSslContext() + throws NoSuchAlgorithmException, KeyManagementException + { + var trustManager = new X509TrustManager() + { + @Override + public X509Certificate[] getAcceptedIssuers() + { + return null; + } + + @Override + public void checkClientTrusted(X509Certificate[] certs, String authType) + { + // no check + } + + @Override + public void checkServerTrusted(X509Certificate[] certs, String authType) + { + // no check + } + }; + + var sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, new TrustManager[] { trustManager }, new SecureRandom()); + return sslContext; } @Override @@ -113,10 +172,12 @@ public boolean isReadyForPrediction(RecommenderContext aContext) @Override public void train(RecommenderContext aContext, List aCasses) throws RecommendationException { - TrainingRequest trainingRequest = new TrainingRequest(); + var client = getClient(); + + var trainingRequest = new TrainingRequest(); // We assume that the type system for all CAS are the same - String typeSystem = serializeTypeSystem(aCasses.get(0)); + var typeSystem = serializeTypeSystem(aCasses.get(0)); trainingRequest.setTypeSystem(typeSystem); // Fill in metadata. We use the type system of the first CAS in the list @@ -128,19 +189,19 @@ public void train(RecommenderContext aContext, List aCasses) throws Recomme trainingRequest.setMetadata(buildMetadata(aCasses.get(0))); - List documents = new ArrayList<>(); - for (CAS cas : aCasses) { + var documents = new ArrayList(); + for (var cas : aCasses) { documents.add(buildDocument(cas)); } trainingRequest.setDocuments(documents); - HttpRequest request = HttpRequest.newBuilder() // + var request = HttpRequest.newBuilder() // .uri(URI.create(appendIfMissing(traits.getRemoteUrl(), "/")).resolve("train")) // .header(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON_VALUE) // .timeout(properties.getReadTimeout()) .POST(BodyPublishers.ofString(toJson(trainingRequest), UTF_8)).build(); - HttpResponse response = sendRequest(request); + HttpResponse response = sendRequest(client, request); if (response.statusCode() == HTTP_TOO_MANY_REQUESTS) { LOG.info("External recommender is already training"); } @@ -161,23 +222,25 @@ else if (response.statusCode() >= HTTP_BAD_REQUEST) { public Range predict(RecommenderContext aContext, CAS aCas, int aBegin, int aEnd) throws RecommendationException { - String typeSystem = serializeTypeSystem(aCas); + var client = getClient(); + + var typeSystem = serializeTypeSystem(aCas); - PredictionRequest predictionRequest = new PredictionRequest(); + var predictionRequest = new PredictionRequest(); predictionRequest.setTypeSystem(typeSystem); predictionRequest.setDocument(buildDocument(aCas)); // Fill in metadata predictionRequest.setMetadata(buildMetadata(aCas)); - HttpRequest request = HttpRequest.newBuilder() // + var request = HttpRequest.newBuilder() // .uri(URI.create(appendIfMissing(traits.getRemoteUrl(), "/")).resolve("predict")) // .header(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON_VALUE) // .timeout(properties.getReadTimeout()) // .POST(BodyPublishers.ofString(toJson(predictionRequest), UTF_8)) // .build(); - HttpResponse response = sendRequest(request); + HttpResponse response = sendRequest(client, request); // If the response indicates that the request was not successful, // then it does not make sense to go on and try to decode the XMI if (response.statusCode() >= HTTP_BAD_REQUEST) { @@ -288,10 +351,11 @@ private String toJson(Object aObject) throws RecommendationException } } - private HttpResponse sendRequest(HttpRequest aRequest) throws RecommendationException + private HttpResponse sendRequest(HttpClient aClient, HttpRequest aRequest) + throws RecommendationException { try { - return client.send(aRequest, BodyHandlers.ofString(UTF_8)); + return aClient.send(aRequest, BodyHandlers.ofString(UTF_8)); } catch (IOException | InterruptedException e) { throw new RecommendationException("Error while sending request: " + e.getMessage(), e); diff --git a/inception/inception-imls-external/src/main/java/de/tudarmstadt/ukp/inception/recommendation/imls/external/v1/ExternalRecommenderTraits.java b/inception/inception-imls-external/src/main/java/de/tudarmstadt/ukp/inception/recommendation/imls/external/v1/ExternalRecommenderTraits.java index 30cc99025b3..a9765586679 100644 --- a/inception/inception-imls-external/src/main/java/de/tudarmstadt/ukp/inception/recommendation/imls/external/v1/ExternalRecommenderTraits.java +++ b/inception/inception-imls-external/src/main/java/de/tudarmstadt/ukp/inception/recommendation/imls/external/v1/ExternalRecommenderTraits.java @@ -29,6 +29,7 @@ public class ExternalRecommenderTraits private String remoteUrl; private boolean trainable; + private boolean verifyCertificates = true; public String getRemoteUrl() { @@ -49,4 +50,14 @@ public void setTrainable(boolean aTrainable) { trainable = aTrainable; } + + public void setVerifyCertificates(boolean aVerifyCertificates) + { + verifyCertificates = aVerifyCertificates; + } + + public boolean isVerifyCertificates() + { + return verifyCertificates; + } } diff --git a/inception/inception-imls-external/src/main/java/de/tudarmstadt/ukp/inception/recommendation/imls/external/v1/ExternalRecommenderTraitsEditor.html b/inception/inception-imls-external/src/main/java/de/tudarmstadt/ukp/inception/recommendation/imls/external/v1/ExternalRecommenderTraitsEditor.html index 5d6ba01691a..59a1a5ad665 100644 --- a/inception/inception-imls-external/src/main/java/de/tudarmstadt/ukp/inception/recommendation/imls/external/v1/ExternalRecommenderTraitsEditor.html +++ b/inception/inception-imls-external/src/main/java/de/tudarmstadt/ukp/inception/recommendation/imls/external/v1/ExternalRecommenderTraitsEditor.html @@ -27,6 +27,16 @@ +
+
+
+ + +
+
+
diff --git a/inception/inception-imls-external/src/main/java/de/tudarmstadt/ukp/inception/recommendation/imls/external/v1/ExternalRecommenderTraitsEditor.java b/inception/inception-imls-external/src/main/java/de/tudarmstadt/ukp/inception/recommendation/imls/external/v1/ExternalRecommenderTraitsEditor.java index c3503dac0b0..4c1c4b496da 100644 --- a/inception/inception-imls-external/src/main/java/de/tudarmstadt/ukp/inception/recommendation/imls/external/v1/ExternalRecommenderTraitsEditor.java +++ b/inception/inception-imls-external/src/main/java/de/tudarmstadt/ukp/inception/recommendation/imls/external/v1/ExternalRecommenderTraitsEditor.java @@ -63,12 +63,16 @@ protected void onSubmit() } }; - TextField remoteUrl = new TextField<>("remoteUrl"); + var remoteUrl = new TextField("remoteUrl"); remoteUrl.setRequired(true); remoteUrl.add(new UrlValidator()); form.add(remoteUrl); - CheckBox trainable = new CheckBox("trainable"); + var verifyCertificates = new CheckBox("verifyCertificates"); + verifyCertificates.setOutputMarkupId(true); + form.add(verifyCertificates); + + var trainable = new CheckBox("trainable"); trainable.setOutputMarkupId(true); trainable.add(new LambdaAjaxFormComponentUpdatingBehavior("change", _target -> _target.add(getTrainingStatesChoice()))); diff --git a/inception/inception-imls-external/src/main/java/de/tudarmstadt/ukp/inception/recommendation/imls/external/v1/ExternalRecommenderTraitsEditor.properties b/inception/inception-imls-external/src/main/java/de/tudarmstadt/ukp/inception/recommendation/imls/external/v1/ExternalRecommenderTraitsEditor.properties index d8543bb880c..4bd43e422ab 100644 --- a/inception/inception-imls-external/src/main/java/de/tudarmstadt/ukp/inception/recommendation/imls/external/v1/ExternalRecommenderTraitsEditor.properties +++ b/inception/inception-imls-external/src/main/java/de/tudarmstadt/ukp/inception/recommendation/imls/external/v1/ExternalRecommenderTraitsEditor.properties @@ -16,3 +16,4 @@ remoteUrl=Remote URL trainable=Trainable +verifyCertificates=Verify certificates