Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(java-client): add VC client builder #33

Merged
merged 1 commit into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,20 @@
*/
package io.carbynestack.thymus.client;

import io.carbynestack.httpclient.CsHttpClientException;
import io.carbynestack.thymus.client.ThymusVCPClient.ThymusVCPClientBuilder;
import io.vavr.Tuple;
import io.vavr.Tuple2;
import io.vavr.collection.Stream;
import io.vavr.concurrent.Future;
import io.vavr.control.Either;
import io.vavr.control.Option;
import io.vavr.control.Try;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import lombok.val;

import java.io.File;
import java.util.*;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
Expand Down Expand Up @@ -139,4 +145,133 @@
});
}

/**
* Provides the bearer token used for authentication.
*/
public interface BearerTokenProvider {

/**
* Returns the bearer token for a Thymus endpoint.
*
* @param endpoint The endpoint of the Thymus service for which the token is requested.
* @return The token
*/
String getBearerToken(ThymusEndpoint endpoint);
}

/**
* Builder class to create a new {@link ThymusVCClient}.
*/
public static class Builder {

private List<ThymusEndpoint> endpoints;
private final List<File> trustedCertificates;
private boolean sslValidationEnabled = true;
private Option<BearerTokenProvider> bearerTokenProvider;
private ThymusVCPClientBuilder thymusClientBuilder = ThymusVCPClient.Builder();

public Builder() {
this.endpoints = new ArrayList<>();
this.trustedCertificates = new ArrayList<>();
bearerTokenProvider = Option.none();
}

/**
* Adds a Thymus service endpoint to the list of endpoints, the client should communicate
* with.
*
* @param endpoint Endpoint of a backend Thymus Service
*/
public Builder withEndpoint(ThymusEndpoint endpoint) {
this.endpoints.add(endpoint);
return this;
}

/**
* The client will be initialized to communicate with the given endpoints. All endpoints that
* have been added before will be replaced. To add additional endpoints use {@link
* #withEndpoint(ThymusEndpoint)}.
*
* @param endpoints A List of endpoints which will be used to communicate with.
*/
public Builder withEndpoints(@NonNull List<ThymusEndpoint> endpoints) {
this.endpoints = new ArrayList<>(endpoints);
return this;
}

/**
* Controls whether SSL certificate validation is performed.
*
* <p>
*
* <p><b>WARNING</b><br>
* Please be aware, that disabling validation leads to insecure web connections and is meant to
* be used in a local test setup only. Using this option in a productive environment is
* explicitly <u>not recommended</u>.
*
* @param enabled <tt>true</tt>, in case SSL certificate validation should happen,
* <tt>false</tt> otherwise
*/
public Builder withSslCertificateValidation(boolean enabled) {
this.sslValidationEnabled = enabled;
return this;
}

/**
* Adds a certificate (.pem) to the trust store.<br>
* This allows tls secured communication with services that do not have a certificate issued by
* an official CA (certificate authority).
*
* @param trustedCertificate Public certificate.
*/
public Builder withTrustedCertificate(File trustedCertificate) {
this.trustedCertificates.add(trustedCertificate);
return this;
}

/**
* Sets a provider for getting a backend specific bearer token that is injected as an
* authorization header to REST HTTP calls emitted by the client.
*
* @param bearerTokenProvider Provider for backend specific bearer token
*/
public Builder withBearerTokenProvider(BearerTokenProvider bearerTokenProvider) {
this.bearerTokenProvider = Option.of(bearerTokenProvider);
return this;

Check warning on line 240 in clients/java/src/main/java/io/carbynestack/thymus/client/ThymusVCClient.java

View check run for this annotation

Codecov / codecov/patch

clients/java/src/main/java/io/carbynestack/thymus/client/ThymusVCClient.java#L239-L240

Added lines #L239 - L240 were not covered by tests
}

protected Builder withThymusVCPClientBuilder(ThymusVCPClientBuilder thymusClientBuilder) {
this.thymusClientBuilder = thymusClientBuilder;
return this;
}

/**
* Builds and returns a new {@link ThymusVCClient} according to the given configuration.
*
* @throws CsHttpClientException If the client could not be instantiated.
*/
public ThymusVCClient build() throws CsHttpClientException {
if (this.endpoints == null || this.endpoints.isEmpty()) {
throw new IllegalArgumentException(

Check warning on line 255 in clients/java/src/main/java/io/carbynestack/thymus/client/ThymusVCClient.java

View check run for this annotation

Codecov / codecov/patch

clients/java/src/main/java/io/carbynestack/thymus/client/ThymusVCClient.java#L255

Added line #L255 was not covered by tests
"At least one Thymus service endpoint has to be provided.");
}
List<ThymusVCPClient> clients = Try.sequence(
this.endpoints.stream().map(endpoint -> Try.of(() -> {
ThymusVCPClientBuilder b =
thymusClientBuilder
.withEndpoint(endpoint)
.withoutSslValidation(!this.sslValidationEnabled)
.withTrustedCertificates(this.trustedCertificates);
this.bearerTokenProvider.forEach(p ->
b.withBearerToken(
Option.of(p.getBearerToken(endpoint))));

Check warning on line 267 in clients/java/src/main/java/io/carbynestack/thymus/client/ThymusVCClient.java

View check run for this annotation

Codecov / codecov/patch

clients/java/src/main/java/io/carbynestack/thymus/client/ThymusVCClient.java#L266-L267

Added lines #L266 - L267 were not covered by tests
return b.build();
}))
.collect(Collectors.toList()))
.getOrElseThrow(CsHttpClientException::new)
.toJavaList();
return new ThymusVCClient(clients);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
* Copyright (c) 2024 - for information on the respective copyright owner
* see the NOTICE file and/or the repository https://github.com/carbynestack/thymus.
*
* SPDX-License-Identifier: Apache-2.0
*/
package io.carbynestack.thymus.client;

import io.carbynestack.httpclient.CsHttpClientException;
import io.carbynestack.thymus.client.ThymusVCPClient.ThymusVCPClientBuilder;
import org.hamcrest.CoreMatchers;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;

import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.*;

@RunWith(MockitoJUnitRunner.class)
public class ThymusVCClientBuilderTest {

private static final List<ThymusEndpoint> ENDPOINTS =
Stream.of("https://testUri:80", "https://testUri:180")
.map(url -> ThymusEndpoint.Builder()
.withServiceUri(URI.create(url))
.build())
.collect(Collectors.toList());

@Mock
private ThymusVCPClientBuilder thymusClientBuilder;

@Test
public void givenEndpointList_whenBuilding_createsClientWithCorrectEndpoints()
throws CsHttpClientException {
ThymusVCClient client =
new ThymusVCClient.Builder().withEndpoints(ENDPOINTS).build();
assertThat(
client.getThymusEndpoints(),
CoreMatchers.hasItems(ENDPOINTS.toArray(new ThymusEndpoint[0])));
}

@Test
public void givenIndividualEndpoints_whenBuilding_createsClientWithCorrectEndpoints()
throws CsHttpClientException {
ThymusVCClient.Builder builder = new ThymusVCClient.Builder();
ENDPOINTS.forEach(builder::withEndpoint);
ThymusVCClient client = builder.build();
assertThat(
client.getThymusEndpoints(),
CoreMatchers.hasItems(ENDPOINTS.toArray(new ThymusEndpoint[0])));
}

@Test
public void
givenSslCertificateValidationDisabledOnBuilder_whenBuilding_createsUnderlyingClientsWithSslCertificateValidationDisabled()
throws CsHttpClientException {
when(thymusClientBuilder.withEndpoint(any())).thenReturn(thymusClientBuilder);
when(thymusClientBuilder.withoutSslValidation(anyBoolean()))
.thenReturn(thymusClientBuilder);
when(thymusClientBuilder.withTrustedCertificates(any())).thenReturn(thymusClientBuilder);
new ThymusVCClient.Builder()
.withThymusVCPClientBuilder(thymusClientBuilder)
.withEndpoints(ENDPOINTS)
.withSslCertificateValidation(false)
.build();
verify(thymusClientBuilder, times(2)).withoutSslValidation(true);
}

@Test
public void
givenTrustedCertificateProvidedToBuilder_whenBuilding_createsUnderlyingClientsWithCertificatesAdded()
throws IOException {
File cert = File.createTempFile("test", ".pem");
when(thymusClientBuilder.withEndpoint(any())).thenReturn(thymusClientBuilder);
when(thymusClientBuilder.withoutSslValidation(anyBoolean()))
.thenReturn(thymusClientBuilder);
when(thymusClientBuilder.withTrustedCertificates(any())).thenReturn(thymusClientBuilder);
new ThymusVCClient.Builder()
.withThymusVCPClientBuilder(thymusClientBuilder)
.withEndpoints(ENDPOINTS)
.withTrustedCertificate(cert)
.build();
verify(thymusClientBuilder, times(2))
.withTrustedCertificates(Collections.singletonList(cert));
}

}
2 changes: 1 addition & 1 deletion release-please-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
},
"clients/java": {
"package-name": "java-client",
"release-type": "java"
"release-type": "maven"
}
}
}
Loading