Skip to content

Commit

Permalink
#4289 - Ability to skip SSL certificate validation on external recomm…
Browse files Browse the repository at this point in the history
…enders

- Added checkbox in external recommender settings that can be used to disable certificate checks
  • Loading branch information
reckart committed Nov 10, 2023
1 parent e910ca1 commit 01bbec3
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -80,14 +89,15 @@ public class ExternalRecommender
{
public static final Key<Boolean> 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)
Expand All @@ -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
Expand All @@ -113,10 +172,12 @@ public boolean isReadyForPrediction(RecommenderContext aContext)
@Override
public void train(RecommenderContext aContext, List<CAS> 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
Expand All @@ -128,19 +189,19 @@ public void train(RecommenderContext aContext, List<CAS> aCasses) throws Recomme

trainingRequest.setMetadata(buildMetadata(aCasses.get(0)));

List<Document> documents = new ArrayList<>();
for (CAS cas : aCasses) {
var documents = new ArrayList<Document>();
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<String> response = sendRequest(request);
HttpResponse<String> response = sendRequest(client, request);
if (response.statusCode() == HTTP_TOO_MANY_REQUESTS) {
LOG.info("External recommender is already training");
}
Expand All @@ -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<String> response = sendRequest(request);
HttpResponse<String> 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) {
Expand Down Expand Up @@ -288,10 +351,11 @@ private String toJson(Object aObject) throws RecommendationException
}
}

private HttpResponse<String> sendRequest(HttpRequest aRequest) throws RecommendationException
private HttpResponse<String> 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public class ExternalRecommenderTraits

private String remoteUrl;
private boolean trainable;
private boolean verifyCertificates = true;

public String getRemoteUrl()
{
Expand All @@ -49,4 +50,14 @@ public void setTrainable(boolean aTrainable)
{
trainable = aTrainable;
}

public void setVerifyCertificates(boolean aVerifyCertificates)
{
verifyCertificates = aVerifyCertificates;
}

public boolean isVerifyCertificates()
{
return verifyCertificates;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,16 @@
<input wicket:id="remoteUrl" type="text" class="form-control"></input>
</div>
</div>
<div class="row form-row" wicket:enclosure="verifyCertificates">
<div class="offset-sm-3 col-sm-9">
<div class="form-check">
<input wicket:id="verifyCertificates" class="form-check-input" type="checkbox"/>
<label wicket:for="verifyCertificates" class="form-check-label">
<wicket:label key="verifyCertificates"/>
</label>
</div>
</div>
</div>
<div class="row form-row" wicket:enclosure="trainable">
<div class="offset-sm-3 col-sm-9">
<div class="form-check">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,16 @@ protected void onSubmit()
}
};

TextField<String> remoteUrl = new TextField<>("remoteUrl");
var remoteUrl = new TextField<String>("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())));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@

remoteUrl=Remote URL
trainable=Trainable
verifyCertificates=Verify certificates

0 comments on commit 01bbec3

Please sign in to comment.