From 3e191871e996ec89b803bd065156acf269fee1cc Mon Sep 17 00:00:00 2001 From: Mark McLaughlin Date: Tue, 20 Aug 2024 11:32:53 +0100 Subject: [PATCH 01/13] Factor out classes by package, make manager code client agnostic. --- .gitignore | 3 + .../client => clients}/CDIManagedClients.java | 10 +- .../clients/ChannelManager.java | 97 ++++++++++++++ .../{relations/client => clients}/Config.java | 2 +- .../project_kessel/clients/KesselClient.java | 16 +++ .../clients/KesselClientsManager.java | 11 ++ .../authn/CallCredentialsFactory.java | 6 +- .../OIDCClientCredentialsCallCredentials.java | 4 +- .../client/OIDCClientCredentialsMinter.java | 7 +- .../NimbusOIDCClientCredentialsMinter.java | 6 +- .../relations/client/CheckClient.java | 9 +- .../relations/client/HealthClient.java | 8 +- .../relations/client/LookupClient.java | 9 +- .../client/RelationTuplesClient.java | 9 +- .../client/RelationsGrpcClientsManager.java | 118 +++--------------- .../relations/example/Caller.java | 2 +- .../CDIManagedClientsContainerTests.java | 5 +- .../CDIManagedClientsTest.java | 3 +- .../client => clients}/ConfigTest.java | 2 +- .../RelationsGrpcClientsManagerTest.java | 48 +++---- .../authn/CallCredentialsFactoryTest.java | 6 +- ...CClientCredentialsCallCredentialsTest.java | 8 +- .../OIDCClientCredentialsMinterTest.java | 7 +- ...NimbusOIDCClientCredentialsMinterTest.java | 6 +- 24 files changed, 228 insertions(+), 174 deletions(-) rename src/main/java/org/project_kessel/{relations/client => clients}/CDIManagedClients.java (77%) create mode 100644 src/main/java/org/project_kessel/clients/ChannelManager.java rename src/main/java/org/project_kessel/{relations/client => clients}/Config.java (96%) create mode 100644 src/main/java/org/project_kessel/clients/KesselClient.java create mode 100644 src/main/java/org/project_kessel/clients/KesselClientsManager.java rename src/main/java/org/project_kessel/{relations/client => clients}/authn/CallCredentialsFactory.java (85%) rename src/main/java/org/project_kessel/{relations/client => clients}/authn/oidc/client/OIDCClientCredentialsCallCredentials.java (97%) rename src/main/java/org/project_kessel/{relations/client => clients}/authn/oidc/client/OIDCClientCredentialsMinter.java (92%) rename src/main/java/org/project_kessel/{relations/client => clients}/authn/oidc/client/nimbus/NimbusOIDCClientCredentialsMinter.java (94%) rename src/test/java/org/project_kessel/{relations/client => clients}/CDIManagedClientsContainerTests.java (95%) rename src/test/java/org/project_kessel/{relations/client => clients}/CDIManagedClientsTest.java (98%) rename src/test/java/org/project_kessel/{relations/client => clients}/ConfigTest.java (96%) rename src/test/java/org/project_kessel/{relations/client => clients}/RelationsGrpcClientsManagerTest.java (89%) rename src/test/java/org/project_kessel/{relations/client => clients}/authn/CallCredentialsFactoryTest.java (88%) rename src/test/java/org/project_kessel/{relations/client => clients}/authn/oidc/client/OIDCClientCredentialsCallCredentialsTest.java (96%) rename src/test/java/org/project_kessel/{relations/client => clients}/authn/oidc/client/OIDCClientCredentialsMinterTest.java (92%) rename src/test/java/org/project_kessel/{relations/client => clients}/authn/oidc/client/nimbus/NimbusOIDCClientCredentialsMinterTest.java (88%) diff --git a/.gitignore b/.gitignore index 8cada35..1763f67 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,6 @@ Thumbs.db # Maven generated artifacts target/ + +# Intellij +*.iml \ No newline at end of file diff --git a/src/main/java/org/project_kessel/relations/client/CDIManagedClients.java b/src/main/java/org/project_kessel/clients/CDIManagedClients.java similarity index 77% rename from src/main/java/org/project_kessel/relations/client/CDIManagedClients.java rename to src/main/java/org/project_kessel/clients/CDIManagedClients.java index dd9c5d1..67e0bec 100644 --- a/src/main/java/org/project_kessel/relations/client/CDIManagedClients.java +++ b/src/main/java/org/project_kessel/clients/CDIManagedClients.java @@ -1,12 +1,16 @@ -package org.project_kessel.relations.client; +package org.project_kessel.clients; import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.inject.Produces; +import org.project_kessel.relations.client.CheckClient; +import org.project_kessel.relations.client.LookupClient; +import org.project_kessel.relations.client.RelationTuplesClient; +import org.project_kessel.relations.client.RelationsGrpcClientsManager; /** - * A managed bean for providing relations api clients for injection in apps. + * A managed bean for providing clients for injection in apps. * It has the current limitation that only one underlying grpc connection can be configured. - * However, it is still possible to create more via RelationsGrpcClientsManager directly. + * However, it is still possible to create more via KesselClientsManager implementation directly. * This class does nothing unless the client is being managed by a CDI container (e.g. Quarkus) */ @ApplicationScoped diff --git a/src/main/java/org/project_kessel/clients/ChannelManager.java b/src/main/java/org/project_kessel/clients/ChannelManager.java new file mode 100644 index 0000000..24fc82e --- /dev/null +++ b/src/main/java/org/project_kessel/clients/ChannelManager.java @@ -0,0 +1,97 @@ +package org.project_kessel.clients; + +import io.grpc.*; +import org.project_kessel.clients.authn.CallCredentialsFactory; + +import java.util.HashMap; + +public final class ChannelManager { + private static ChannelManager channelManager; + + private final HashMap insecureManagers = new HashMap<>(); + private final HashMap secureManagers = new HashMap<>(); + + public static ChannelManager instance() { + if(channelManager == null) { + channelManager = new ChannelManager(); + } + + return channelManager; + } + + public synchronized Channel forInsecureClients(String targetUrl) { + if (!insecureManagers.containsKey(targetUrl)) { + var channel = Grpc.newChannelBuilder(targetUrl, InsecureChannelCredentials.create()).build(); + insecureManagers.put(targetUrl, channel); + } + return insecureManagers.get(targetUrl); + } + + public synchronized Channel forInsecureClients(String targetUrl, Config.AuthenticationConfig authnConfig) throws RuntimeException { + if (!insecureManagers.containsKey(targetUrl)) { + try { + var channel = Grpc.newChannelBuilder(targetUrl, + CompositeChannelCredentials.create(InsecureChannelCredentials.create(), CallCredentialsFactory.create(authnConfig))).build(); + insecureManagers.put(targetUrl, channel); + } catch (CallCredentialsFactory.CallCredentialsCreationException e) { + throw new RuntimeException(e); + } + } + return insecureManagers.get(targetUrl); + } + + public synchronized Channel forSecureClients(String targetUrl) { + if (!secureManagers.containsKey(targetUrl)) { + var tlsChannelCredentials = TlsChannelCredentials.create(); + var channel = Grpc.newChannelBuilder(targetUrl, tlsChannelCredentials).build(); + secureManagers.put(targetUrl, channel); + } + return secureManagers.get(targetUrl); + } + + public synchronized Channel forSecureClients(String targetUrl, Config.AuthenticationConfig authnConfig) { + if (!secureManagers.containsKey(targetUrl)) { + var tlsChannelCredentials = TlsChannelCredentials.create(); + try { + var channel = Grpc.newChannelBuilder(targetUrl, + CompositeChannelCredentials.create(tlsChannelCredentials, CallCredentialsFactory.create(authnConfig))).build(); + secureManagers.put(targetUrl, channel); + } catch (CallCredentialsFactory.CallCredentialsCreationException e) { + throw new RuntimeException(e); + } + } + return secureManagers.get(targetUrl); + } + + public synchronized void shutdownAll() { + for (var channel : insecureManagers.values()) { + channel.shutdown(); + } + insecureManagers.clear(); + for (var channel : secureManagers.values()) { + channel.shutdown(); + } + secureManagers.clear(); + } + + public synchronized void shutdownChannel(Channel channelToShutdown) { + var iter = insecureManagers.entrySet().iterator(); + while (iter.hasNext()) { + var entry = iter.next(); + if(entry.getValue() == channelToShutdown) { + entry.getValue().shutdown(); + iter.remove(); + return; + } + } + iter = secureManagers.entrySet().iterator(); + while (iter.hasNext()) { + var entry = iter.next(); + if(entry.getValue() == channelToShutdown) { + entry.getValue().shutdown(); + iter.remove(); + return; + } + } + } +} diff --git a/src/main/java/org/project_kessel/relations/client/Config.java b/src/main/java/org/project_kessel/clients/Config.java similarity index 96% rename from src/main/java/org/project_kessel/relations/client/Config.java rename to src/main/java/org/project_kessel/clients/Config.java index 96b3ecd..0cbaeb3 100644 --- a/src/main/java/org/project_kessel/relations/client/Config.java +++ b/src/main/java/org/project_kessel/clients/Config.java @@ -1,4 +1,4 @@ -package org.project_kessel.relations.client; +package org.project_kessel.clients; import io.smallrye.config.ConfigMapping; import io.smallrye.config.WithDefault; diff --git a/src/main/java/org/project_kessel/clients/KesselClient.java b/src/main/java/org/project_kessel/clients/KesselClient.java new file mode 100644 index 0000000..ebf6da7 --- /dev/null +++ b/src/main/java/org/project_kessel/clients/KesselClient.java @@ -0,0 +1,16 @@ +package org.project_kessel.clients; + +import io.grpc.stub.AbstractAsyncStub; +import io.grpc.stub.AbstractBlockingStub; + +public abstract class KesselClient, B extends AbstractBlockingStub> { + protected A asyncStub; + protected B blockingStub; + + protected KesselClient(A asyncStub, B blockingStub) { + this.asyncStub = asyncStub; + this.blockingStub = blockingStub; + } + + +} diff --git a/src/main/java/org/project_kessel/clients/KesselClientsManager.java b/src/main/java/org/project_kessel/clients/KesselClientsManager.java new file mode 100644 index 0000000..d79f4e4 --- /dev/null +++ b/src/main/java/org/project_kessel/clients/KesselClientsManager.java @@ -0,0 +1,11 @@ +package org.project_kessel.clients; + +import io.grpc.Channel; + +public abstract class KesselClientsManager { + protected final Channel channel; + + protected KesselClientsManager(Channel channel) { + this.channel = channel; + } +} diff --git a/src/main/java/org/project_kessel/relations/client/authn/CallCredentialsFactory.java b/src/main/java/org/project_kessel/clients/authn/CallCredentialsFactory.java similarity index 85% rename from src/main/java/org/project_kessel/relations/client/authn/CallCredentialsFactory.java rename to src/main/java/org/project_kessel/clients/authn/CallCredentialsFactory.java index cd07d4e..75bfcb5 100644 --- a/src/main/java/org/project_kessel/relations/client/authn/CallCredentialsFactory.java +++ b/src/main/java/org/project_kessel/clients/authn/CallCredentialsFactory.java @@ -1,8 +1,8 @@ -package org.project_kessel.relations.client.authn; +package org.project_kessel.clients.authn; import io.grpc.CallCredentials; -import org.project_kessel.relations.client.Config; -import org.project_kessel.relations.client.authn.oidc.client.OIDCClientCredentialsCallCredentials; +import org.project_kessel.clients.Config; +import org.project_kessel.clients.authn.oidc.client.OIDCClientCredentialsCallCredentials; public class CallCredentialsFactory { diff --git a/src/main/java/org/project_kessel/relations/client/authn/oidc/client/OIDCClientCredentialsCallCredentials.java b/src/main/java/org/project_kessel/clients/authn/oidc/client/OIDCClientCredentialsCallCredentials.java similarity index 97% rename from src/main/java/org/project_kessel/relations/client/authn/oidc/client/OIDCClientCredentialsCallCredentials.java rename to src/main/java/org/project_kessel/clients/authn/oidc/client/OIDCClientCredentialsCallCredentials.java index 8c05648..701d6fc 100644 --- a/src/main/java/org/project_kessel/relations/client/authn/oidc/client/OIDCClientCredentialsCallCredentials.java +++ b/src/main/java/org/project_kessel/clients/authn/oidc/client/OIDCClientCredentialsCallCredentials.java @@ -1,8 +1,8 @@ -package org.project_kessel.relations.client.authn.oidc.client; +package org.project_kessel.clients.authn.oidc.client; import io.grpc.Metadata; import io.grpc.Status; -import org.project_kessel.relations.client.Config; +import org.project_kessel.clients.Config; import java.util.Optional; import java.util.concurrent.Executor; diff --git a/src/main/java/org/project_kessel/relations/client/authn/oidc/client/OIDCClientCredentialsMinter.java b/src/main/java/org/project_kessel/clients/authn/oidc/client/OIDCClientCredentialsMinter.java similarity index 92% rename from src/main/java/org/project_kessel/relations/client/authn/oidc/client/OIDCClientCredentialsMinter.java rename to src/main/java/org/project_kessel/clients/authn/oidc/client/OIDCClientCredentialsMinter.java index 10dfc9a..93906a6 100644 --- a/src/main/java/org/project_kessel/relations/client/authn/oidc/client/OIDCClientCredentialsMinter.java +++ b/src/main/java/org/project_kessel/clients/authn/oidc/client/OIDCClientCredentialsMinter.java @@ -1,6 +1,7 @@ -package org.project_kessel.relations.client.authn.oidc.client; +package org.project_kessel.clients.authn.oidc.client; -import org.project_kessel.relations.client.Config; +import org.project_kessel.clients.Config; +import org.project_kessel.clients.authn.oidc.client.nimbus.NimbusOIDCClientCredentialsMinter; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; @@ -8,7 +9,7 @@ import java.util.Optional; public abstract class OIDCClientCredentialsMinter { - private static final Class defaultMinter = org.project_kessel.relations.client.authn.oidc.client.nimbus.NimbusOIDCClientCredentialsMinter.class; + private static final Class defaultMinter = NimbusOIDCClientCredentialsMinter.class; public static OIDCClientCredentialsMinter forDefaultImplementation() throws OIDCClientCredentialsMinterException { return forClass(defaultMinter); diff --git a/src/main/java/org/project_kessel/relations/client/authn/oidc/client/nimbus/NimbusOIDCClientCredentialsMinter.java b/src/main/java/org/project_kessel/clients/authn/oidc/client/nimbus/NimbusOIDCClientCredentialsMinter.java similarity index 94% rename from src/main/java/org/project_kessel/relations/client/authn/oidc/client/nimbus/NimbusOIDCClientCredentialsMinter.java rename to src/main/java/org/project_kessel/clients/authn/oidc/client/nimbus/NimbusOIDCClientCredentialsMinter.java index 378ea43..4237aca 100644 --- a/src/main/java/org/project_kessel/relations/client/authn/oidc/client/nimbus/NimbusOIDCClientCredentialsMinter.java +++ b/src/main/java/org/project_kessel/clients/authn/oidc/client/nimbus/NimbusOIDCClientCredentialsMinter.java @@ -1,4 +1,4 @@ -package org.project_kessel.relations.client.authn.oidc.client.nimbus; +package org.project_kessel.clients.authn.oidc.client.nimbus; import com.nimbusds.oauth2.sdk.*; import com.nimbusds.oauth2.sdk.auth.ClientAuthentication; @@ -11,8 +11,8 @@ import com.nimbusds.openid.connect.sdk.OIDCTokenResponseParser; import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata; import io.quarkus.runtime.annotations.RegisterForReflection; -import org.project_kessel.relations.client.Config; -import org.project_kessel.relations.client.authn.oidc.client.OIDCClientCredentialsMinter; +import org.project_kessel.clients.Config; +import org.project_kessel.clients.authn.oidc.client.OIDCClientCredentialsMinter; import java.io.IOException; import java.net.URI; diff --git a/src/main/java/org/project_kessel/relations/client/CheckClient.java b/src/main/java/org/project_kessel/relations/client/CheckClient.java index 38ae86e..2d85a49 100644 --- a/src/main/java/org/project_kessel/relations/client/CheckClient.java +++ b/src/main/java/org/project_kessel/relations/client/CheckClient.java @@ -7,18 +7,15 @@ import io.grpc.stub.StreamObserver; import io.smallrye.mutiny.Uni; import io.smallrye.mutiny.operators.multi.processors.UnicastProcessor; +import org.project_kessel.clients.KesselClient; import java.util.logging.Logger; -public class CheckClient { +public class CheckClient extends KesselClient { private static final Logger logger = Logger.getLogger(CheckClient.class.getName()); - private final KesselCheckServiceGrpc.KesselCheckServiceStub asyncStub; - private final KesselCheckServiceGrpc.KesselCheckServiceBlockingStub blockingStub; - CheckClient(Channel channel) { - asyncStub = KesselCheckServiceGrpc.newStub(channel); - blockingStub = KesselCheckServiceGrpc.newBlockingStub(channel); + super(KesselCheckServiceGrpc.newStub(channel), KesselCheckServiceGrpc.newBlockingStub(channel)); } public void check(CheckRequest request, diff --git a/src/main/java/org/project_kessel/relations/client/HealthClient.java b/src/main/java/org/project_kessel/relations/client/HealthClient.java index 9a76c93..e8ed135 100644 --- a/src/main/java/org/project_kessel/relations/client/HealthClient.java +++ b/src/main/java/org/project_kessel/relations/client/HealthClient.java @@ -8,14 +8,14 @@ import org.project_kessel.api.relations.v1.GetReadyzResponse; import org.project_kessel.api.relations.v1.GetReadyzRequest; import org.project_kessel.api.relations.v1.KesselHealthServiceGrpc; +import org.project_kessel.api.relations.v1beta1.KesselCheckServiceGrpc; +import org.project_kessel.clients.KesselClient; -public class HealthClient { +public class HealthClient extends KesselClient { private static final Logger logger = Logger.getLogger(HealthClient.class.getName()); - private final KesselHealthServiceGrpc.KesselHealthServiceBlockingStub blockingStub; - HealthClient(Channel channel) { - blockingStub = KesselHealthServiceGrpc.newBlockingStub(channel); + super(KesselHealthServiceGrpc.newStub(channel), KesselHealthServiceGrpc.newBlockingStub(channel)); } public GetReadyzResponse readyz(GetReadyzRequest request) { diff --git a/src/main/java/org/project_kessel/relations/client/LookupClient.java b/src/main/java/org/project_kessel/relations/client/LookupClient.java index 49b1ed4..59cc549 100644 --- a/src/main/java/org/project_kessel/relations/client/LookupClient.java +++ b/src/main/java/org/project_kessel/relations/client/LookupClient.java @@ -5,19 +5,16 @@ import io.smallrye.mutiny.Multi; import io.smallrye.mutiny.operators.multi.processors.UnicastProcessor; import org.project_kessel.api.relations.v1beta1.*; +import org.project_kessel.clients.KesselClient; import java.util.Iterator; import java.util.logging.Logger; -public class LookupClient { +public class LookupClient extends KesselClient { private static final Logger logger = Logger.getLogger(LookupClient.class.getName()); - private final KesselLookupServiceGrpc.KesselLookupServiceStub asyncStub; - private final KesselLookupServiceGrpc.KesselLookupServiceBlockingStub blockingStub; - LookupClient(Channel channel) { - asyncStub = KesselLookupServiceGrpc.newStub(channel); - blockingStub = KesselLookupServiceGrpc.newBlockingStub(channel); + super(KesselLookupServiceGrpc.newStub(channel), KesselLookupServiceGrpc.newBlockingStub(channel)); } public void lookupSubjects(LookupSubjectsRequest request, StreamObserver responseObserver) { diff --git a/src/main/java/org/project_kessel/relations/client/RelationTuplesClient.java b/src/main/java/org/project_kessel/relations/client/RelationTuplesClient.java index cdb7b89..9fc0bea 100644 --- a/src/main/java/org/project_kessel/relations/client/RelationTuplesClient.java +++ b/src/main/java/org/project_kessel/relations/client/RelationTuplesClient.java @@ -11,16 +11,13 @@ import io.grpc.stub.StreamObserver; import io.smallrye.mutiny.Multi; import io.smallrye.mutiny.operators.multi.processors.UnicastProcessor; +import org.project_kessel.clients.KesselClient; import java.util.Iterator; -public class RelationTuplesClient { - private final KesselTupleServiceGrpc.KesselTupleServiceStub asyncStub; - private final KesselTupleServiceGrpc.KesselTupleServiceBlockingStub blockingStub; - +public class RelationTuplesClient extends KesselClient { RelationTuplesClient(Channel channel) { - asyncStub = KesselTupleServiceGrpc.newStub(channel); - blockingStub = KesselTupleServiceGrpc.newBlockingStub(channel); + super(KesselTupleServiceGrpc.newStub(channel), KesselTupleServiceGrpc.newBlockingStub(channel)); } /** diff --git a/src/main/java/org/project_kessel/relations/client/RelationsGrpcClientsManager.java b/src/main/java/org/project_kessel/relations/client/RelationsGrpcClientsManager.java index 4629ab4..0afb666 100644 --- a/src/main/java/org/project_kessel/relations/client/RelationsGrpcClientsManager.java +++ b/src/main/java/org/project_kessel/relations/client/RelationsGrpcClientsManager.java @@ -1,116 +1,37 @@ package org.project_kessel.relations.client; -import io.grpc.*; -import org.project_kessel.relations.client.authn.CallCredentialsFactory; +import io.grpc.Channel; +import org.project_kessel.clients.ChannelManager; +import org.project_kessel.clients.Config; +import org.project_kessel.clients.KesselClientsManager; -import java.util.HashMap; - -public class RelationsGrpcClientsManager { - private static final HashMap insecureManagers = new HashMap<>(); - private static final HashMap secureManagers = new HashMap<>(); - - private final ManagedChannel channel; - - public static synchronized RelationsGrpcClientsManager forInsecureClients(String targetUrl) { - if (!insecureManagers.containsKey(targetUrl)) { - var manager = new RelationsGrpcClientsManager(targetUrl, InsecureChannelCredentials.create()); - insecureManagers.put(targetUrl, manager); - } - return insecureManagers.get(targetUrl); - } - - public static synchronized RelationsGrpcClientsManager forInsecureClients(String targetUrl, Config.AuthenticationConfig authnConfig) throws RuntimeException { - if (!insecureManagers.containsKey(targetUrl)) { - try { - var manager = new RelationsGrpcClientsManager(targetUrl, - InsecureChannelCredentials.create(), - CallCredentialsFactory.create(authnConfig)); - insecureManagers.put(targetUrl, manager); - } catch (CallCredentialsFactory.CallCredentialsCreationException e) { - throw new RuntimeException(e); - } - } - return insecureManagers.get(targetUrl); - } - - public static synchronized RelationsGrpcClientsManager forSecureClients(String targetUrl) { - if (!secureManagers.containsKey(targetUrl)) { - var tlsChannelCredentials = TlsChannelCredentials.create(); - var manager = new RelationsGrpcClientsManager(targetUrl, tlsChannelCredentials); - secureManagers.put(targetUrl, manager); - } - return secureManagers.get(targetUrl); +public final class RelationsGrpcClientsManager extends KesselClientsManager { + private RelationsGrpcClientsManager(Channel channel) { + super(channel); } - public static synchronized RelationsGrpcClientsManager forSecureClients(String targetUrl, Config.AuthenticationConfig authnConfig) { - if (!secureManagers.containsKey(targetUrl)) { - var tlsChannelCredentials = TlsChannelCredentials.create(); - try { - var manager = new RelationsGrpcClientsManager(targetUrl, - tlsChannelCredentials, - CallCredentialsFactory.create(authnConfig)); - secureManagers.put(targetUrl, manager); - } catch (CallCredentialsFactory.CallCredentialsCreationException e) { - throw new RuntimeException(e); - } - } - return secureManagers.get(targetUrl); + public static RelationsGrpcClientsManager forInsecureClients(String targetUrl) { + return new RelationsGrpcClientsManager(ChannelManager.instance().forInsecureClients(targetUrl)); } - public static synchronized void shutdownAll() { - for (var manager : insecureManagers.values()) { - manager.closeClientChannel(); - } - insecureManagers.clear(); - for (var manager : secureManagers.values()) { - manager.closeClientChannel(); - } - secureManagers.clear(); + public static RelationsGrpcClientsManager forInsecureClients(String targetUrl, Config.AuthenticationConfig authnConfig) throws RuntimeException { + return new RelationsGrpcClientsManager(ChannelManager.instance().forInsecureClients(targetUrl, authnConfig)); } - public static synchronized void shutdownManager(RelationsGrpcClientsManager managerToShutdown) { - var iter = insecureManagers.entrySet().iterator(); - while (iter.hasNext()) { - var entry = iter.next(); - if(entry.getValue().channel == managerToShutdown.channel) { - entry.getValue().closeClientChannel(); - iter.remove(); - return; - } - } - iter = secureManagers.entrySet().iterator(); - while (iter.hasNext()) { - var entry = iter.next(); - if(entry.getValue().channel == managerToShutdown.channel) { - entry.getValue().closeClientChannel(); - iter.remove(); - return; - } - } + public static RelationsGrpcClientsManager forSecureClients(String targetUrl) { + return new RelationsGrpcClientsManager(ChannelManager.instance().forSecureClients(targetUrl)); } - /** - * Create a manager for a grpc channel with server credentials. - * @param targetUrl - * @param serverCredentials authenticates the server for TLS or are InsecureChannelCredentials - */ - private RelationsGrpcClientsManager(String targetUrl, ChannelCredentials serverCredentials) { - this.channel = Grpc.newChannelBuilder(targetUrl, serverCredentials).build(); + public static RelationsGrpcClientsManager forSecureClients(String targetUrl, Config.AuthenticationConfig authnConfig) { + return new RelationsGrpcClientsManager(ChannelManager.instance().forSecureClients(targetUrl, authnConfig)); } - /** - * Create a manager for a grpc channel with server credentials and credentials for per-rpc client authentication. - * @param targetUrl - * @param serverCredentials authenticates the server for TLS or are InsecureChannelCredentials - * @param authnCredentials authenticates the client on each rpc - */ - private RelationsGrpcClientsManager(String targetUrl, ChannelCredentials serverCredentials, CallCredentials authnCredentials) { - this.channel = Grpc.newChannelBuilder(targetUrl, - CompositeChannelCredentials.create(serverCredentials, authnCredentials)).build(); + public static void shutdownAll() { + ChannelManager.instance().shutdownAll(); } - private void closeClientChannel() { - channel.shutdown(); + public static void shutdownManager(RelationsGrpcClientsManager managerToShutdown) { + ChannelManager.instance().shutdownChannel(managerToShutdown.channel); } public CheckClient getCheckClient() { @@ -128,5 +49,4 @@ public LookupClient getLookupClient() { public HealthClient getHealthClient() { return new HealthClient(channel); } - } diff --git a/src/main/java/org/project_kessel/relations/example/Caller.java b/src/main/java/org/project_kessel/relations/example/Caller.java index 6ac2cd3..56e32bf 100644 --- a/src/main/java/org/project_kessel/relations/example/Caller.java +++ b/src/main/java/org/project_kessel/relations/example/Caller.java @@ -3,10 +3,10 @@ import org.project_kessel.api.relations.v1beta1.*; import org.project_kessel.api.relations.v1.GetLivezRequest; import org.project_kessel.api.relations.v1.GetReadyzRequest; -import org.project_kessel.relations.client.RelationsGrpcClientsManager; import io.grpc.stub.StreamObserver; import io.smallrye.mutiny.Multi; import io.smallrye.mutiny.Uni; +import org.project_kessel.relations.client.RelationsGrpcClientsManager; import java.util.ArrayList; import java.util.Collections; diff --git a/src/test/java/org/project_kessel/relations/client/CDIManagedClientsContainerTests.java b/src/test/java/org/project_kessel/clients/CDIManagedClientsContainerTests.java similarity index 95% rename from src/test/java/org/project_kessel/relations/client/CDIManagedClientsContainerTests.java rename to src/test/java/org/project_kessel/clients/CDIManagedClientsContainerTests.java index fd5ca01..4a29673 100644 --- a/src/test/java/org/project_kessel/relations/client/CDIManagedClientsContainerTests.java +++ b/src/test/java/org/project_kessel/clients/CDIManagedClientsContainerTests.java @@ -1,4 +1,4 @@ -package org.project_kessel.relations.client; +package org.project_kessel.clients; import org.project_kessel.api.relations.v1beta1.*; import io.grpc.Server; @@ -13,6 +13,9 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.project_kessel.relations.client.CheckClient; +import org.project_kessel.relations.client.LookupClient; +import org.project_kessel.relations.client.RelationTuplesClient; import java.io.IOException; import java.util.Optional; diff --git a/src/test/java/org/project_kessel/relations/client/CDIManagedClientsTest.java b/src/test/java/org/project_kessel/clients/CDIManagedClientsTest.java similarity index 98% rename from src/test/java/org/project_kessel/relations/client/CDIManagedClientsTest.java rename to src/test/java/org/project_kessel/clients/CDIManagedClientsTest.java index 2258d60..45cd98c 100644 --- a/src/test/java/org/project_kessel/relations/client/CDIManagedClientsTest.java +++ b/src/test/java/org/project_kessel/clients/CDIManagedClientsTest.java @@ -1,8 +1,9 @@ -package org.project_kessel.relations.client; +package org.project_kessel.clients; import org.junit.jupiter.api.Test; import org.mockito.MockedStatic; import org.mockito.Mockito; +import org.project_kessel.relations.client.RelationsGrpcClientsManager; import java.util.Optional; diff --git a/src/test/java/org/project_kessel/relations/client/ConfigTest.java b/src/test/java/org/project_kessel/clients/ConfigTest.java similarity index 96% rename from src/test/java/org/project_kessel/relations/client/ConfigTest.java rename to src/test/java/org/project_kessel/clients/ConfigTest.java index 5a2f67b..834b517 100644 --- a/src/test/java/org/project_kessel/relations/client/ConfigTest.java +++ b/src/test/java/org/project_kessel/clients/ConfigTest.java @@ -1,4 +1,4 @@ -package org.project_kessel.relations.client; +package org.project_kessel.clients; import io.smallrye.config.SmallRyeConfigBuilder; diff --git a/src/test/java/org/project_kessel/relations/client/RelationsGrpcClientsManagerTest.java b/src/test/java/org/project_kessel/clients/RelationsGrpcClientsManagerTest.java similarity index 89% rename from src/test/java/org/project_kessel/relations/client/RelationsGrpcClientsManagerTest.java rename to src/test/java/org/project_kessel/clients/RelationsGrpcClientsManagerTest.java index 1c72739..ed8b36e 100644 --- a/src/test/java/org/project_kessel/relations/client/RelationsGrpcClientsManagerTest.java +++ b/src/test/java/org/project_kessel/clients/RelationsGrpcClientsManagerTest.java @@ -1,4 +1,4 @@ -package org.project_kessel.relations.client; +package org.project_kessel.clients; import io.grpc.Metadata; import org.junit.jupiter.api.AfterAll; @@ -9,6 +9,7 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.project_kessel.relations.client.RelationsGrpcClientsManager; import org.project_kessel.relations.client.fake.GrpcServerSpy; import java.util.HashMap; @@ -46,7 +47,7 @@ static void removeTestSetup() { } @Test - void testManagerReusePatterns() { + void testManagerChannelReusePatterns() { var one = RelationsGrpcClientsManager.forInsecureClients("localhost:8080"); var two = RelationsGrpcClientsManager.forInsecureClients("localhost:8080"); // same as one var three = RelationsGrpcClientsManager.forInsecureClients("localhost1:8080"); @@ -60,10 +61,10 @@ void testManagerReusePatterns() { assertNotNull(four); assertNotNull(five); assertNotNull(six); - assertEquals(one, two); - assertNotEquals(two, three); - assertEquals(five, six); - assertNotEquals(four, five); + assertEquals(one.channel, two.channel); + assertNotEquals(two.channel, three.channel); + assertEquals(five.channel, six.channel); + assertNotEquals(four.channel, five.channel); } @Test @@ -72,7 +73,7 @@ void testThreadingChaos() { * creating and destroying managers on different threads. */ try { - Hashtable managers = new Hashtable<>(); + Hashtable managers = new Hashtable<>(); int numberOfThreads = 100; ExecutorService service = Executors.newFixedThreadPool(numberOfThreads); @@ -178,12 +179,14 @@ void testManagerReuseInternal() throws Exception { RelationsGrpcClientsManager.forSecureClients("localhost1:8080"); RelationsGrpcClientsManager.forSecureClients("localhost1:8080"); // same as five - var insecureField = RelationsGrpcClientsManager.class.getDeclaredField("insecureManagers"); + var kesselChannelManager = ChannelManager.instance(); + + var insecureField = ChannelManager.class.getDeclaredField("insecureManagers"); insecureField.setAccessible(true); - var secureField = RelationsGrpcClientsManager.class.getDeclaredField("secureManagers"); + var secureField = ChannelManager.class.getDeclaredField("secureManagers"); secureField.setAccessible(true); - var insecureManagers = (HashMap)insecureField.get(null); - var secureManagers = (HashMap)secureField.get(null); + var insecureManagers = (HashMap)insecureField.get(kesselChannelManager); + var secureManagers = (HashMap)secureField.get(kesselChannelManager); assertEquals(2, insecureManagers.size()); assertEquals(2, secureManagers.size()); @@ -196,13 +199,13 @@ void testSameChannelUsedByClientsInternal() throws Exception { var relationTuplesClient = manager.getRelationTuplesClient(); var lookupClient = manager.getLookupClient(); - var checkAsyncStubField = CheckClient.class.getDeclaredField("asyncStub"); + var checkAsyncStubField = KesselClient.class.getDeclaredField("asyncStub"); checkAsyncStubField.setAccessible(true); var checkChannel = ((KesselCheckServiceGrpc.KesselCheckServiceStub)checkAsyncStubField.get(checkClient)).getChannel(); - var relationTuplesAsyncStubField = RelationTuplesClient.class.getDeclaredField("asyncStub"); + var relationTuplesAsyncStubField = KesselClient.class.getDeclaredField("asyncStub"); relationTuplesAsyncStubField.setAccessible(true); var relationTuplesChannel = ((KesselTupleServiceGrpc.KesselTupleServiceStub)relationTuplesAsyncStubField.get(relationTuplesClient)).getChannel(); - var lookupAsyncStubField = LookupClient.class.getDeclaredField("asyncStub"); + var lookupAsyncStubField = KesselClient.class.getDeclaredField("asyncStub"); lookupAsyncStubField.setAccessible(true); var lookupChannel = ((KesselLookupServiceGrpc.KesselLookupServiceStub)lookupAsyncStubField.get(lookupClient)).getChannel(); @@ -212,36 +215,37 @@ void testSameChannelUsedByClientsInternal() throws Exception { @Test void testCreateAndShutdownPatternsInternal() throws Exception { - var insecureField = RelationsGrpcClientsManager.class.getDeclaredField("insecureManagers"); + var kesselChannelManager = ChannelManager.instance(); + var insecureField = ChannelManager.class.getDeclaredField("insecureManagers"); insecureField.setAccessible(true); - var insecureManagersSize = ((HashMap)insecureField.get(null)).size(); + var insecureManagersSize = ((HashMap)insecureField.get(kesselChannelManager)).size(); assertEquals(0, insecureManagersSize); var manager = RelationsGrpcClientsManager.forInsecureClients("localhost:8080"); - insecureManagersSize = ((HashMap)insecureField.get(null)).size(); + insecureManagersSize = ((HashMap)insecureField.get(kesselChannelManager)).size(); assertEquals(1, insecureManagersSize); RelationsGrpcClientsManager.shutdownManager(manager); - insecureManagersSize = ((HashMap)insecureField.get(null)).size(); + insecureManagersSize = ((HashMap)insecureField.get(kesselChannelManager)).size(); assertEquals(0, insecureManagersSize); /* Shouldn't throw exception if executed twice */ RelationsGrpcClientsManager.shutdownManager(manager); - insecureManagersSize = ((HashMap)insecureField.get(null)).size(); + insecureManagersSize = ((HashMap)insecureField.get(kesselChannelManager)).size(); assertEquals(0, insecureManagersSize); var manager2 = RelationsGrpcClientsManager.forInsecureClients("localhost:8080"); - insecureManagersSize = ((HashMap)insecureField.get(null)).size(); + insecureManagersSize = ((HashMap)insecureField.get(kesselChannelManager)).size(); assertEquals(1, insecureManagersSize); assertNotEquals(manager, manager2); RelationsGrpcClientsManager.forInsecureClients("localhost:8081"); - insecureManagersSize = ((HashMap)insecureField.get(null)).size(); + insecureManagersSize = ((HashMap)insecureField.get(kesselChannelManager)).size(); assertEquals(2, insecureManagersSize); RelationsGrpcClientsManager.shutdownAll(); - insecureManagersSize = ((HashMap)insecureField.get(null)).size(); + insecureManagersSize = ((HashMap)insecureField.get(kesselChannelManager)).size(); assertEquals(0, insecureManagersSize); } diff --git a/src/test/java/org/project_kessel/relations/client/authn/CallCredentialsFactoryTest.java b/src/test/java/org/project_kessel/clients/authn/CallCredentialsFactoryTest.java similarity index 88% rename from src/test/java/org/project_kessel/relations/client/authn/CallCredentialsFactoryTest.java rename to src/test/java/org/project_kessel/clients/authn/CallCredentialsFactoryTest.java index 426f24a..5f0c064 100644 --- a/src/test/java/org/project_kessel/relations/client/authn/CallCredentialsFactoryTest.java +++ b/src/test/java/org/project_kessel/clients/authn/CallCredentialsFactoryTest.java @@ -1,12 +1,12 @@ -package org.project_kessel.relations.client.authn; +package org.project_kessel.clients.authn; import org.junit.jupiter.api.Test; -import org.project_kessel.relations.client.Config; +import org.project_kessel.clients.Config; import java.util.Optional; import static org.junit.jupiter.api.Assertions.fail; -import static org.project_kessel.relations.client.RelationsGrpcClientsManagerTest.dummyAuthConfigWithGoodOIDCClientCredentials; +import static org.project_kessel.clients.RelationsGrpcClientsManagerTest.dummyAuthConfigWithGoodOIDCClientCredentials; class CallCredentialsFactoryTest { diff --git a/src/test/java/org/project_kessel/relations/client/authn/oidc/client/OIDCClientCredentialsCallCredentialsTest.java b/src/test/java/org/project_kessel/clients/authn/oidc/client/OIDCClientCredentialsCallCredentialsTest.java similarity index 96% rename from src/test/java/org/project_kessel/relations/client/authn/oidc/client/OIDCClientCredentialsCallCredentialsTest.java rename to src/test/java/org/project_kessel/clients/authn/oidc/client/OIDCClientCredentialsCallCredentialsTest.java index 0d92453..31b1aca 100644 --- a/src/test/java/org/project_kessel/relations/client/authn/oidc/client/OIDCClientCredentialsCallCredentialsTest.java +++ b/src/test/java/org/project_kessel/clients/authn/oidc/client/OIDCClientCredentialsCallCredentialsTest.java @@ -1,11 +1,13 @@ -package org.project_kessel.relations.client.authn.oidc.client; +package org.project_kessel.clients.authn.oidc.client; import io.grpc.CallCredentials; import io.grpc.Metadata; import io.grpc.Status; import io.grpc.netty.shaded.io.netty.util.concurrent.DefaultEventExecutor; import org.junit.jupiter.api.Test; -import org.project_kessel.relations.client.Config; +import org.project_kessel.clients.Config; +import org.project_kessel.clients.authn.oidc.client.OIDCClientCredentialsCallCredentials; +import org.project_kessel.clients.authn.oidc.client.OIDCClientCredentialsMinter; import java.time.LocalDateTime; import java.util.Optional; @@ -13,7 +15,7 @@ import java.util.concurrent.atomic.AtomicReference; import static org.junit.jupiter.api.Assertions.*; -import static org.project_kessel.relations.client.authn.oidc.client.OIDCClientCredentialsMinter.getDefaultMinterImplementation; +import static org.project_kessel.clients.authn.oidc.client.OIDCClientCredentialsMinter.getDefaultMinterImplementation; public class OIDCClientCredentialsCallCredentialsTest { diff --git a/src/test/java/org/project_kessel/relations/client/authn/oidc/client/OIDCClientCredentialsMinterTest.java b/src/test/java/org/project_kessel/clients/authn/oidc/client/OIDCClientCredentialsMinterTest.java similarity index 92% rename from src/test/java/org/project_kessel/relations/client/authn/oidc/client/OIDCClientCredentialsMinterTest.java rename to src/test/java/org/project_kessel/clients/authn/oidc/client/OIDCClientCredentialsMinterTest.java index 8bc9d10..50564ae 100644 --- a/src/test/java/org/project_kessel/relations/client/authn/oidc/client/OIDCClientCredentialsMinterTest.java +++ b/src/test/java/org/project_kessel/clients/authn/oidc/client/OIDCClientCredentialsMinterTest.java @@ -1,13 +1,14 @@ -package org.project_kessel.relations.client.authn.oidc.client; +package org.project_kessel.clients.authn.oidc.client; import org.junit.jupiter.api.Test; -import org.project_kessel.relations.client.Config; +import org.project_kessel.clients.Config; +import org.project_kessel.clients.authn.oidc.client.OIDCClientCredentialsMinter; import java.time.LocalDateTime; import java.util.Optional; import static org.junit.jupiter.api.Assertions.*; -import static org.project_kessel.relations.client.authn.oidc.client.OIDCClientCredentialsMinter.getExpiryDateFromExpiresIn; +import static org.project_kessel.clients.authn.oidc.client.OIDCClientCredentialsMinter.getExpiryDateFromExpiresIn; class OIDCClientCredentialsMinterTest { diff --git a/src/test/java/org/project_kessel/relations/client/authn/oidc/client/nimbus/NimbusOIDCClientCredentialsMinterTest.java b/src/test/java/org/project_kessel/clients/authn/oidc/client/nimbus/NimbusOIDCClientCredentialsMinterTest.java similarity index 88% rename from src/test/java/org/project_kessel/relations/client/authn/oidc/client/nimbus/NimbusOIDCClientCredentialsMinterTest.java rename to src/test/java/org/project_kessel/clients/authn/oidc/client/nimbus/NimbusOIDCClientCredentialsMinterTest.java index bc83e26..4d90f56 100644 --- a/src/test/java/org/project_kessel/relations/client/authn/oidc/client/nimbus/NimbusOIDCClientCredentialsMinterTest.java +++ b/src/test/java/org/project_kessel/clients/authn/oidc/client/nimbus/NimbusOIDCClientCredentialsMinterTest.java @@ -1,8 +1,8 @@ -package org.project_kessel.relations.client.authn.oidc.client.nimbus; +package org.project_kessel.clients.authn.oidc.client.nimbus; import org.junit.jupiter.api.Test; -import org.project_kessel.relations.client.RelationsGrpcClientsManagerTest; -import org.project_kessel.relations.client.authn.oidc.client.OIDCClientCredentialsMinter; +import org.project_kessel.clients.RelationsGrpcClientsManagerTest; +import org.project_kessel.clients.authn.oidc.client.OIDCClientCredentialsMinter; import org.project_kessel.relations.client.fake.FakeIdp; import static org.junit.jupiter.api.Assertions.*; From 7a117fa3688a5fbc1901f900cfdfff8efb6c1055 Mon Sep 17 00:00:00 2001 From: Mark McLaughlin Date: Tue, 20 Aug 2024 14:36:45 +0100 Subject: [PATCH 02/13] Make CDIManagedClients client specific. --- .../client/CDIManagedRelationsClients.java} | 9 +++----- ...anagedRelationsClientsContainerTests.java} | 2 +- .../CDIManagedRelationsClientsTest.java} | 22 +++++++++---------- 3 files changed, 15 insertions(+), 18 deletions(-) rename src/main/java/org/project_kessel/{clients/CDIManagedClients.java => relations/client/CDIManagedRelationsClients.java} (84%) rename src/test/java/org/project_kessel/clients/{CDIManagedClientsContainerTests.java => CDIManagedRelationsClientsContainerTests.java} (99%) rename src/test/java/org/project_kessel/{clients/CDIManagedClientsTest.java => relations/client/CDIManagedRelationsClientsTest.java} (89%) diff --git a/src/main/java/org/project_kessel/clients/CDIManagedClients.java b/src/main/java/org/project_kessel/relations/client/CDIManagedRelationsClients.java similarity index 84% rename from src/main/java/org/project_kessel/clients/CDIManagedClients.java rename to src/main/java/org/project_kessel/relations/client/CDIManagedRelationsClients.java index 67e0bec..26ff06a 100644 --- a/src/main/java/org/project_kessel/clients/CDIManagedClients.java +++ b/src/main/java/org/project_kessel/relations/client/CDIManagedRelationsClients.java @@ -1,11 +1,8 @@ -package org.project_kessel.clients; +package org.project_kessel.relations.client; import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.inject.Produces; -import org.project_kessel.relations.client.CheckClient; -import org.project_kessel.relations.client.LookupClient; -import org.project_kessel.relations.client.RelationTuplesClient; -import org.project_kessel.relations.client.RelationsGrpcClientsManager; +import org.project_kessel.clients.Config; /** * A managed bean for providing clients for injection in apps. @@ -14,7 +11,7 @@ * This class does nothing unless the client is being managed by a CDI container (e.g. Quarkus) */ @ApplicationScoped -public class CDIManagedClients { +public class CDIManagedRelationsClients { @Produces RelationsGrpcClientsManager getManager(Config config) { var isSecureClients = config.isSecureClients(); diff --git a/src/test/java/org/project_kessel/clients/CDIManagedClientsContainerTests.java b/src/test/java/org/project_kessel/clients/CDIManagedRelationsClientsContainerTests.java similarity index 99% rename from src/test/java/org/project_kessel/clients/CDIManagedClientsContainerTests.java rename to src/test/java/org/project_kessel/clients/CDIManagedRelationsClientsContainerTests.java index 4a29673..e702e79 100644 --- a/src/test/java/org/project_kessel/clients/CDIManagedClientsContainerTests.java +++ b/src/test/java/org/project_kessel/clients/CDIManagedRelationsClientsContainerTests.java @@ -26,7 +26,7 @@ * Use Weld as a test container to check CDI functionality. */ @EnableWeld -class CDIManagedClientsContainerTests { +class CDIManagedRelationsClientsContainerTests { @WeldSetup public WeldInitiator weld = WeldInitiator.from(new Weld().setBeanDiscoveryMode(BeanDiscoveryMode.ALL).addBeanClass(TestConfig.class)).build(); diff --git a/src/test/java/org/project_kessel/clients/CDIManagedClientsTest.java b/src/test/java/org/project_kessel/relations/client/CDIManagedRelationsClientsTest.java similarity index 89% rename from src/test/java/org/project_kessel/clients/CDIManagedClientsTest.java rename to src/test/java/org/project_kessel/relations/client/CDIManagedRelationsClientsTest.java index 45cd98c..d5f4e1a 100644 --- a/src/test/java/org/project_kessel/clients/CDIManagedClientsTest.java +++ b/src/test/java/org/project_kessel/relations/client/CDIManagedRelationsClientsTest.java @@ -1,22 +1,22 @@ -package org.project_kessel.clients; +package org.project_kessel.relations.client; import org.junit.jupiter.api.Test; import org.mockito.MockedStatic; import org.mockito.Mockito; -import org.project_kessel.relations.client.RelationsGrpcClientsManager; +import org.project_kessel.clients.Config; import java.util.Optional; import static org.mockito.Mockito.*; -class CDIManagedClientsTest { +class CDIManagedRelationsClientsTest { @Test void testInsecureNoAuthnMakesCorrectManagerCall() { Config config = makeDummyConfig(false, makeDummyAuthenticationConfig(false)); - CDIManagedClients cdiManagedClients = new CDIManagedClients(); + CDIManagedRelationsClients cdiManagedRelationsClients = new CDIManagedRelationsClients(); try (MockedStatic dummyManager = Mockito.mockStatic(RelationsGrpcClientsManager.class)) { - cdiManagedClients.getManager(config); + cdiManagedRelationsClients.getManager(config); dummyManager.verify( () -> RelationsGrpcClientsManager.forInsecureClients(anyString()), times(1) @@ -39,10 +39,10 @@ void testInsecureNoAuthnMakesCorrectManagerCall() { @Test void testInsecureWithAuthnMakesCorrectManagerCall() { Config config = makeDummyConfig(false, makeDummyAuthenticationConfig(true)); - CDIManagedClients cdiManagedClients = new CDIManagedClients(); + CDIManagedRelationsClients cdiManagedRelationsClients = new CDIManagedRelationsClients(); try (MockedStatic dummyManager = Mockito.mockStatic(RelationsGrpcClientsManager.class)) { - cdiManagedClients.getManager(config); + cdiManagedRelationsClients.getManager(config); dummyManager.verify( () -> RelationsGrpcClientsManager.forInsecureClients(anyString()), times(0) @@ -65,10 +65,10 @@ void testInsecureWithAuthnMakesCorrectManagerCall() { @Test void testSecureNoAuthnMakesCorrectManagerCall() { Config config = makeDummyConfig(true, makeDummyAuthenticationConfig(false)); - CDIManagedClients cdiManagedClients = new CDIManagedClients(); + CDIManagedRelationsClients cdiManagedRelationsClients = new CDIManagedRelationsClients(); try (MockedStatic dummyManager = Mockito.mockStatic(RelationsGrpcClientsManager.class)) { - cdiManagedClients.getManager(config); + cdiManagedRelationsClients.getManager(config); dummyManager.verify( () -> RelationsGrpcClientsManager.forInsecureClients(anyString()), times(0) @@ -91,10 +91,10 @@ void testSecureNoAuthnMakesCorrectManagerCall() { @Test void testSecureWithAuthnMakesCorrectManagerCall() { Config config = makeDummyConfig(true, makeDummyAuthenticationConfig(true)); - CDIManagedClients cdiManagedClients = new CDIManagedClients(); + CDIManagedRelationsClients cdiManagedRelationsClients = new CDIManagedRelationsClients(); try (MockedStatic dummyManager = Mockito.mockStatic(RelationsGrpcClientsManager.class)) { - cdiManagedClients.getManager(config); + cdiManagedRelationsClients.getManager(config); dummyManager.verify( () -> RelationsGrpcClientsManager.forInsecureClients(anyString()), times(0) From a0348574bc1db7c1a36543e2c6e32b3547ce1004 Mon Sep 17 00:00:00 2001 From: Mark McLaughlin Date: Tue, 20 Aug 2024 16:52:31 +0100 Subject: [PATCH 03/13] Further refactoring to put classes in the right place. --- .../clients/KesselClientsManager.java | 14 ++++++++++++++ .../authn/CallCredentialsFactoryTest.java | 2 +- .../NimbusOIDCClientCredentialsMinterTest.java | 4 ++-- .../client => clients}/fake/FakeIdp.java | 2 +- .../client => clients}/fake/GrpcServerSpy.java | 2 +- ...CDIManagedRelationsClientsContainerTests.java | 6 ++---- .../client}/RelationsGrpcClientsManagerTest.java | 16 +++++++++------- 7 files changed, 30 insertions(+), 16 deletions(-) rename src/test/java/org/project_kessel/{relations/client => clients}/fake/FakeIdp.java (98%) rename src/test/java/org/project_kessel/{relations/client => clients}/fake/GrpcServerSpy.java (99%) rename src/test/java/org/project_kessel/{clients => relations/client}/CDIManagedRelationsClientsContainerTests.java (95%) rename src/test/java/org/project_kessel/{clients => relations/client}/RelationsGrpcClientsManagerTest.java (97%) diff --git a/src/main/java/org/project_kessel/clients/KesselClientsManager.java b/src/main/java/org/project_kessel/clients/KesselClientsManager.java index d79f4e4..17f3d59 100644 --- a/src/main/java/org/project_kessel/clients/KesselClientsManager.java +++ b/src/main/java/org/project_kessel/clients/KesselClientsManager.java @@ -8,4 +8,18 @@ public abstract class KesselClientsManager { protected KesselClientsManager(Channel channel) { this.channel = channel; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + KesselClientsManager that = (KesselClientsManager) o; + return channel.equals(that.channel); + } + + @Override + public int hashCode() { + return channel.hashCode(); + } } diff --git a/src/test/java/org/project_kessel/clients/authn/CallCredentialsFactoryTest.java b/src/test/java/org/project_kessel/clients/authn/CallCredentialsFactoryTest.java index 5f0c064..f758c04 100644 --- a/src/test/java/org/project_kessel/clients/authn/CallCredentialsFactoryTest.java +++ b/src/test/java/org/project_kessel/clients/authn/CallCredentialsFactoryTest.java @@ -6,7 +6,7 @@ import java.util.Optional; import static org.junit.jupiter.api.Assertions.fail; -import static org.project_kessel.clients.RelationsGrpcClientsManagerTest.dummyAuthConfigWithGoodOIDCClientCredentials; +import static org.project_kessel.relations.client.RelationsGrpcClientsManagerTest.dummyAuthConfigWithGoodOIDCClientCredentials; class CallCredentialsFactoryTest { diff --git a/src/test/java/org/project_kessel/clients/authn/oidc/client/nimbus/NimbusOIDCClientCredentialsMinterTest.java b/src/test/java/org/project_kessel/clients/authn/oidc/client/nimbus/NimbusOIDCClientCredentialsMinterTest.java index 4d90f56..068eaf6 100644 --- a/src/test/java/org/project_kessel/clients/authn/oidc/client/nimbus/NimbusOIDCClientCredentialsMinterTest.java +++ b/src/test/java/org/project_kessel/clients/authn/oidc/client/nimbus/NimbusOIDCClientCredentialsMinterTest.java @@ -1,9 +1,9 @@ package org.project_kessel.clients.authn.oidc.client.nimbus; import org.junit.jupiter.api.Test; -import org.project_kessel.clients.RelationsGrpcClientsManagerTest; +import org.project_kessel.relations.client.RelationsGrpcClientsManagerTest; import org.project_kessel.clients.authn.oidc.client.OIDCClientCredentialsMinter; -import org.project_kessel.relations.client.fake.FakeIdp; +import org.project_kessel.clients.fake.FakeIdp; import static org.junit.jupiter.api.Assertions.*; diff --git a/src/test/java/org/project_kessel/relations/client/fake/FakeIdp.java b/src/test/java/org/project_kessel/clients/fake/FakeIdp.java similarity index 98% rename from src/test/java/org/project_kessel/relations/client/fake/FakeIdp.java rename to src/test/java/org/project_kessel/clients/fake/FakeIdp.java index b481455..386254b 100644 --- a/src/test/java/org/project_kessel/relations/client/fake/FakeIdp.java +++ b/src/test/java/org/project_kessel/clients/fake/FakeIdp.java @@ -1,4 +1,4 @@ -package org.project_kessel.relations.client.fake; +package org.project_kessel.clients.fake; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; diff --git a/src/test/java/org/project_kessel/relations/client/fake/GrpcServerSpy.java b/src/test/java/org/project_kessel/clients/fake/GrpcServerSpy.java similarity index 99% rename from src/test/java/org/project_kessel/relations/client/fake/GrpcServerSpy.java rename to src/test/java/org/project_kessel/clients/fake/GrpcServerSpy.java index e7b0e95..35c1bfc 100644 --- a/src/test/java/org/project_kessel/relations/client/fake/GrpcServerSpy.java +++ b/src/test/java/org/project_kessel/clients/fake/GrpcServerSpy.java @@ -1,4 +1,4 @@ -package org.project_kessel.relations.client.fake; +package org.project_kessel.clients.fake; import io.grpc.*; import io.grpc.stub.StreamObserver; diff --git a/src/test/java/org/project_kessel/clients/CDIManagedRelationsClientsContainerTests.java b/src/test/java/org/project_kessel/relations/client/CDIManagedRelationsClientsContainerTests.java similarity index 95% rename from src/test/java/org/project_kessel/clients/CDIManagedRelationsClientsContainerTests.java rename to src/test/java/org/project_kessel/relations/client/CDIManagedRelationsClientsContainerTests.java index e702e79..13bb8f0 100644 --- a/src/test/java/org/project_kessel/clients/CDIManagedRelationsClientsContainerTests.java +++ b/src/test/java/org/project_kessel/relations/client/CDIManagedRelationsClientsContainerTests.java @@ -1,4 +1,4 @@ -package org.project_kessel.clients; +package org.project_kessel.relations.client; import org.project_kessel.api.relations.v1beta1.*; import io.grpc.Server; @@ -13,9 +13,7 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.project_kessel.relations.client.CheckClient; -import org.project_kessel.relations.client.LookupClient; -import org.project_kessel.relations.client.RelationTuplesClient; +import org.project_kessel.clients.Config; import java.io.IOException; import java.util.Optional; diff --git a/src/test/java/org/project_kessel/clients/RelationsGrpcClientsManagerTest.java b/src/test/java/org/project_kessel/relations/client/RelationsGrpcClientsManagerTest.java similarity index 97% rename from src/test/java/org/project_kessel/clients/RelationsGrpcClientsManagerTest.java rename to src/test/java/org/project_kessel/relations/client/RelationsGrpcClientsManagerTest.java index ed8b36e..397bba3 100644 --- a/src/test/java/org/project_kessel/clients/RelationsGrpcClientsManagerTest.java +++ b/src/test/java/org/project_kessel/relations/client/RelationsGrpcClientsManagerTest.java @@ -1,4 +1,4 @@ -package org.project_kessel.clients; +package org.project_kessel.relations.client; import io.grpc.Metadata; import org.junit.jupiter.api.AfterAll; @@ -9,8 +9,10 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.project_kessel.relations.client.RelationsGrpcClientsManager; -import org.project_kessel.relations.client.fake.GrpcServerSpy; +import org.project_kessel.clients.ChannelManager; +import org.project_kessel.clients.Config; +import org.project_kessel.clients.KesselClient; +import org.project_kessel.clients.fake.GrpcServerSpy; import java.util.HashMap; import java.util.Hashtable; @@ -61,10 +63,10 @@ void testManagerChannelReusePatterns() { assertNotNull(four); assertNotNull(five); assertNotNull(six); - assertEquals(one.channel, two.channel); - assertNotEquals(two.channel, three.channel); - assertEquals(five.channel, six.channel); - assertNotEquals(four.channel, five.channel); + assertEquals(one, two); + assertNotEquals(two, three); + assertEquals(five, six); + assertNotEquals(four, five); } @Test From 18b65fc85d8e7e8fa09ca6b18adfa37f584c7c0e Mon Sep 17 00:00:00 2001 From: Mark McLaughlin Date: Tue, 20 Aug 2024 17:50:33 +0100 Subject: [PATCH 04/13] Small changes. --- .../org/project_kessel/clients/KesselClientsManager.java | 8 ++++++++ .../relations/client/RelationsGrpcClientsManager.java | 8 -------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/project_kessel/clients/KesselClientsManager.java b/src/main/java/org/project_kessel/clients/KesselClientsManager.java index 17f3d59..f6c17bb 100644 --- a/src/main/java/org/project_kessel/clients/KesselClientsManager.java +++ b/src/main/java/org/project_kessel/clients/KesselClientsManager.java @@ -9,6 +9,14 @@ protected KesselClientsManager(Channel channel) { this.channel = channel; } + public static void shutdownAll() { + ChannelManager.instance().shutdownAll(); + } + + public static void shutdownManager(KesselClientsManager managerToShutdown) { + ChannelManager.instance().shutdownChannel(managerToShutdown.channel); + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/src/main/java/org/project_kessel/relations/client/RelationsGrpcClientsManager.java b/src/main/java/org/project_kessel/relations/client/RelationsGrpcClientsManager.java index 0afb666..5820878 100644 --- a/src/main/java/org/project_kessel/relations/client/RelationsGrpcClientsManager.java +++ b/src/main/java/org/project_kessel/relations/client/RelationsGrpcClientsManager.java @@ -26,14 +26,6 @@ public static RelationsGrpcClientsManager forSecureClients(String targetUrl, Con return new RelationsGrpcClientsManager(ChannelManager.instance().forSecureClients(targetUrl, authnConfig)); } - public static void shutdownAll() { - ChannelManager.instance().shutdownAll(); - } - - public static void shutdownManager(RelationsGrpcClientsManager managerToShutdown) { - ChannelManager.instance().shutdownChannel(managerToShutdown.channel); - } - public CheckClient getCheckClient() { return new CheckClient(channel); } From 6f899ec8410e6eaa88c010f882961430bf0be075 Mon Sep 17 00:00:00 2001 From: Mark McLaughlin Date: Thu, 22 Aug 2024 11:10:30 +0100 Subject: [PATCH 05/13] Decouple client config and common authn config. --- .../clients/ChannelManager.java | 5 +- .../clients/authn/AuthenticationConfig.java | 18 +++++ .../clients/authn/CallCredentialsFactory.java | 3 +- ...ClientCredentialsAuthenticationConfig.java | 65 +++++++++++++++++++ .../OIDCClientCredentialsCallCredentials.java | 27 ++++---- .../client/OIDCClientCredentialsMinter.java | 3 +- .../NimbusOIDCClientCredentialsMinter.java | 4 +- .../client/AuthnConfigConverter.java | 34 ++++++++++ .../client/CDIManagedRelationsClients.java | 4 +- .../{clients => relations/client}/Config.java | 8 +-- .../client/RelationsGrpcClientsManager.java | 5 +- .../authn/CallCredentialsFactoryTest.java | 37 ++++++----- ...CClientCredentialsCallCredentialsTest.java | 64 ++++++------------ .../OIDCClientCredentialsMinterTest.java | 4 +- ...NimbusOIDCClientCredentialsMinterTest.java | 10 +-- .../client => clients}/util/CertUtil.java | 2 +- ...ManagedRelationsClientsContainerTests.java | 1 - .../CDIManagedRelationsClientsTest.java | 8 +-- .../client}/ConfigTest.java | 2 +- .../RelationsGrpcClientsManagerTest.java | 10 +-- 20 files changed, 203 insertions(+), 111 deletions(-) create mode 100644 src/main/java/org/project_kessel/clients/authn/AuthenticationConfig.java create mode 100644 src/main/java/org/project_kessel/clients/authn/oidc/client/OIDCClientCredentialsAuthenticationConfig.java create mode 100644 src/main/java/org/project_kessel/relations/client/AuthnConfigConverter.java rename src/main/java/org/project_kessel/{clients => relations/client}/Config.java (91%) rename src/test/java/org/project_kessel/{relations/client => clients}/util/CertUtil.java (98%) rename src/test/java/org/project_kessel/{clients => relations/client}/ConfigTest.java (96%) diff --git a/src/main/java/org/project_kessel/clients/ChannelManager.java b/src/main/java/org/project_kessel/clients/ChannelManager.java index 24fc82e..70dfe02 100644 --- a/src/main/java/org/project_kessel/clients/ChannelManager.java +++ b/src/main/java/org/project_kessel/clients/ChannelManager.java @@ -1,6 +1,7 @@ package org.project_kessel.clients; import io.grpc.*; +import org.project_kessel.clients.authn.AuthenticationConfig; import org.project_kessel.clients.authn.CallCredentialsFactory; import java.util.HashMap; @@ -27,7 +28,7 @@ public synchronized Channel forInsecureClients(String targetUrl) { return insecureManagers.get(targetUrl); } - public synchronized Channel forInsecureClients(String targetUrl, Config.AuthenticationConfig authnConfig) throws RuntimeException { + public synchronized Channel forInsecureClients(String targetUrl, AuthenticationConfig authnConfig) throws RuntimeException { if (!insecureManagers.containsKey(targetUrl)) { try { var channel = Grpc.newChannelBuilder(targetUrl, @@ -49,7 +50,7 @@ public synchronized Channel forSecureClients(String targetUrl) { return secureManagers.get(targetUrl); } - public synchronized Channel forSecureClients(String targetUrl, Config.AuthenticationConfig authnConfig) { + public synchronized Channel forSecureClients(String targetUrl, AuthenticationConfig authnConfig) { if (!secureManagers.containsKey(targetUrl)) { var tlsChannelCredentials = TlsChannelCredentials.create(); try { diff --git a/src/main/java/org/project_kessel/clients/authn/AuthenticationConfig.java b/src/main/java/org/project_kessel/clients/authn/AuthenticationConfig.java new file mode 100644 index 0000000..40aa4d6 --- /dev/null +++ b/src/main/java/org/project_kessel/clients/authn/AuthenticationConfig.java @@ -0,0 +1,18 @@ +package org.project_kessel.clients.authn; + +public class AuthenticationConfig { + public enum AuthMode { + DISABLED, + OIDC_CLIENT_CREDENTIALS + } + + private AuthMode authMode; + + public AuthMode mode() { + return authMode; + } + + public void setMode(AuthMode authMode) { + this.authMode = authMode; + } +} diff --git a/src/main/java/org/project_kessel/clients/authn/CallCredentialsFactory.java b/src/main/java/org/project_kessel/clients/authn/CallCredentialsFactory.java index 75bfcb5..a3303a7 100644 --- a/src/main/java/org/project_kessel/clients/authn/CallCredentialsFactory.java +++ b/src/main/java/org/project_kessel/clients/authn/CallCredentialsFactory.java @@ -1,7 +1,6 @@ package org.project_kessel.clients.authn; import io.grpc.CallCredentials; -import org.project_kessel.clients.Config; import org.project_kessel.clients.authn.oidc.client.OIDCClientCredentialsCallCredentials; public class CallCredentialsFactory { @@ -10,7 +9,7 @@ private CallCredentialsFactory() { } - public static CallCredentials create(Config.AuthenticationConfig authnConfig) throws CallCredentialsCreationException { + public static CallCredentials create(AuthenticationConfig authnConfig) throws CallCredentialsCreationException { if (authnConfig == null) { throw new CallCredentialsCreationException("AuthenticationConfig is required to create CallCredentials and must not be null."); } diff --git a/src/main/java/org/project_kessel/clients/authn/oidc/client/OIDCClientCredentialsAuthenticationConfig.java b/src/main/java/org/project_kessel/clients/authn/oidc/client/OIDCClientCredentialsAuthenticationConfig.java new file mode 100644 index 0000000..4ab1791 --- /dev/null +++ b/src/main/java/org/project_kessel/clients/authn/oidc/client/OIDCClientCredentialsAuthenticationConfig.java @@ -0,0 +1,65 @@ +package org.project_kessel.clients.authn.oidc.client; + +import org.project_kessel.clients.authn.AuthenticationConfig; + +import java.util.Optional; + +public class OIDCClientCredentialsAuthenticationConfig extends AuthenticationConfig { + OIDCClientCredentialsConfig credentialsConfig; + + public OIDCClientCredentialsConfig clientCredentialsConfig() { + return credentialsConfig; + } + + public void setCredentialsConfig(OIDCClientCredentialsConfig credentialsConfig) { + this.credentialsConfig = credentialsConfig; + } + + public static class OIDCClientCredentialsConfig { + String issuer; + String clientId; + String clientSecret; + Optional scope; + Optional oidcClientCredentialsMinterImplementation; + + public String issuer() { + return issuer; + } + + public void setIssuer(String issuer) { + this.issuer = issuer; + } + + public String clientId() { + return clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + + public String clientSecret() { + return clientSecret; + } + + public void setClientSecret(String clientSecret) { + this.clientSecret = clientSecret; + } + + public Optional scope() { + return scope; + } + + public void setScope(Optional scope) { + this.scope = scope; + } + + public Optional oidcClientCredentialsMinterImplementation() { + return oidcClientCredentialsMinterImplementation; + } + + public void setOidcClientCredentialsMinterImplementation(Optional oidcClientCredentialsMinterImplementation) { + this.oidcClientCredentialsMinterImplementation = oidcClientCredentialsMinterImplementation; + } + } +} diff --git a/src/main/java/org/project_kessel/clients/authn/oidc/client/OIDCClientCredentialsCallCredentials.java b/src/main/java/org/project_kessel/clients/authn/oidc/client/OIDCClientCredentialsCallCredentials.java index 701d6fc..e0da0d1 100644 --- a/src/main/java/org/project_kessel/clients/authn/oidc/client/OIDCClientCredentialsCallCredentials.java +++ b/src/main/java/org/project_kessel/clients/authn/oidc/client/OIDCClientCredentialsCallCredentials.java @@ -2,7 +2,7 @@ import io.grpc.Metadata; import io.grpc.Status; -import org.project_kessel.clients.Config; +import org.project_kessel.clients.authn.AuthenticationConfig; import java.util.Optional; import java.util.concurrent.Executor; @@ -11,12 +11,12 @@ public class OIDCClientCredentialsCallCredentials extends io.grpc.CallCredentials { static final Metadata.Key authorizationKey = Metadata.Key.of("Authorization", Metadata.ASCII_STRING_MARSHALLER); - private final Config.OIDCClientCredentialsConfig clientCredentialsConfig; + private final OIDCClientCredentialsAuthenticationConfig.OIDCClientCredentialsConfig clientCredentialsConfig; private final OIDCClientCredentialsMinter minter; private final AtomicReference storedBearerHeaderRef = new AtomicReference<>(); - public OIDCClientCredentialsCallCredentials(Config.AuthenticationConfig authnConfig) throws OIDCClientCredentialsCallCredentialsException { + public OIDCClientCredentialsCallCredentials(AuthenticationConfig authnConfig) throws OIDCClientCredentialsCallCredentialsException { this.clientCredentialsConfig = validateAndExtractConfig(authnConfig); Optional minterImpl = clientCredentialsConfig.oidcClientCredentialsMinterImplementation(); @@ -31,7 +31,7 @@ public OIDCClientCredentialsCallCredentials(Config.AuthenticationConfig authnCon } } - OIDCClientCredentialsCallCredentials(Config.OIDCClientCredentialsConfig clientCredentialsConfig, OIDCClientCredentialsMinter minter) { + OIDCClientCredentialsCallCredentials(OIDCClientCredentialsAuthenticationConfig.OIDCClientCredentialsConfig clientCredentialsConfig, OIDCClientCredentialsMinter minter) { this.clientCredentialsConfig = clientCredentialsConfig; this.minter = minter; } @@ -64,22 +64,27 @@ public void flushStoredCredentials() { } } - /* We don't know that smallrye config validation will be used by clients, so do some validation here. */ - static Config.OIDCClientCredentialsConfig validateAndExtractConfig(Config.AuthenticationConfig authnConfig) throws OIDCClientCredentialsCallCredentialsException { - if (authnConfig.clientCredentialsConfig().isEmpty()) { + static OIDCClientCredentialsAuthenticationConfig.OIDCClientCredentialsConfig validateAndExtractConfig(AuthenticationConfig authnConfig) throws OIDCClientCredentialsCallCredentialsException { + if (!(authnConfig instanceof OIDCClientCredentialsAuthenticationConfig)) { throw new OIDCClientCredentialsCallCredentialsException("ClientCredentialsConfig is required for OIDC client credentials authentication method."); } - if(authnConfig.clientCredentialsConfig().get().issuer() == null) { + + var oidcClientCredentialsAuthenticationConfig = (OIDCClientCredentialsAuthenticationConfig) authnConfig; + if(oidcClientCredentialsAuthenticationConfig.clientCredentialsConfig() == null) { + throw new OIDCClientCredentialsCallCredentialsException("ClientCredentialsConfig cannot be null."); + } + + if(oidcClientCredentialsAuthenticationConfig.clientCredentialsConfig().issuer() == null) { throw new OIDCClientCredentialsCallCredentialsException("ClientCredentialsConfig Issuer must not be null."); } - if(authnConfig.clientCredentialsConfig().get().clientId() == null) { + if(oidcClientCredentialsAuthenticationConfig.clientCredentialsConfig().clientId() == null) { throw new OIDCClientCredentialsCallCredentialsException("ClientCredentialsConfig Client id must not be null."); } - if(authnConfig.clientCredentialsConfig().get().clientSecret() == null) { + if(oidcClientCredentialsAuthenticationConfig.clientCredentialsConfig().clientSecret() == null) { throw new OIDCClientCredentialsCallCredentialsException("ClientCredentialsConfig Client secret must not be null."); } - return authnConfig.clientCredentialsConfig().get(); + return oidcClientCredentialsAuthenticationConfig.clientCredentialsConfig(); } public static class OIDCClientCredentialsCallCredentialsException extends Exception { diff --git a/src/main/java/org/project_kessel/clients/authn/oidc/client/OIDCClientCredentialsMinter.java b/src/main/java/org/project_kessel/clients/authn/oidc/client/OIDCClientCredentialsMinter.java index 93906a6..e64f5cc 100644 --- a/src/main/java/org/project_kessel/clients/authn/oidc/client/OIDCClientCredentialsMinter.java +++ b/src/main/java/org/project_kessel/clients/authn/oidc/client/OIDCClientCredentialsMinter.java @@ -1,6 +1,5 @@ package org.project_kessel.clients.authn.oidc.client; -import org.project_kessel.clients.Config; import org.project_kessel.clients.authn.oidc.client.nimbus.NimbusOIDCClientCredentialsMinter; import java.lang.reflect.Constructor; @@ -33,7 +32,7 @@ public static OIDCClientCredentialsMinter forName(String name) throws OIDCClient } } - public abstract BearerHeader authenticateAndRetrieveAuthorizationHeader(Config.OIDCClientCredentialsConfig clientConfig) throws OIDCClientCredentialsMinterException; + public abstract BearerHeader authenticateAndRetrieveAuthorizationHeader(OIDCClientCredentialsAuthenticationConfig.OIDCClientCredentialsConfig clientConfig) throws OIDCClientCredentialsMinterException; public static class BearerHeader { private final String authorizationHeader; diff --git a/src/main/java/org/project_kessel/clients/authn/oidc/client/nimbus/NimbusOIDCClientCredentialsMinter.java b/src/main/java/org/project_kessel/clients/authn/oidc/client/nimbus/NimbusOIDCClientCredentialsMinter.java index 4237aca..74cad0c 100644 --- a/src/main/java/org/project_kessel/clients/authn/oidc/client/nimbus/NimbusOIDCClientCredentialsMinter.java +++ b/src/main/java/org/project_kessel/clients/authn/oidc/client/nimbus/NimbusOIDCClientCredentialsMinter.java @@ -11,7 +11,7 @@ import com.nimbusds.openid.connect.sdk.OIDCTokenResponseParser; import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata; import io.quarkus.runtime.annotations.RegisterForReflection; -import org.project_kessel.clients.Config; +import org.project_kessel.clients.authn.oidc.client.OIDCClientCredentialsAuthenticationConfig; import org.project_kessel.clients.authn.oidc.client.OIDCClientCredentialsMinter; import java.io.IOException; @@ -25,7 +25,7 @@ @RegisterForReflection public class NimbusOIDCClientCredentialsMinter extends OIDCClientCredentialsMinter { @Override - public BearerHeader authenticateAndRetrieveAuthorizationHeader(Config.OIDCClientCredentialsConfig config) throws OIDCClientCredentialsMinterException { + public BearerHeader authenticateAndRetrieveAuthorizationHeader(OIDCClientCredentialsAuthenticationConfig.OIDCClientCredentialsConfig config) throws OIDCClientCredentialsMinterException { Issuer issuer = new Issuer(config.issuer()); ClientID clientID = new ClientID(config.clientId()); Secret clientSecret = new Secret(config.clientSecret()); diff --git a/src/main/java/org/project_kessel/relations/client/AuthnConfigConverter.java b/src/main/java/org/project_kessel/relations/client/AuthnConfigConverter.java new file mode 100644 index 0000000..4c05c7e --- /dev/null +++ b/src/main/java/org/project_kessel/relations/client/AuthnConfigConverter.java @@ -0,0 +1,34 @@ +package org.project_kessel.relations.client; + +import org.project_kessel.clients.authn.AuthenticationConfig; +import org.project_kessel.clients.authn.oidc.client.OIDCClientCredentialsAuthenticationConfig; +import org.project_kessel.clients.authn.oidc.client.OIDCClientCredentialsAuthenticationConfig.OIDCClientCredentialsConfig; + +public class AuthnConfigConverter { + + public static AuthenticationConfig convert(Config.AuthenticationConfig authnConfig) { + if(authnConfig == null) { + return null; + } + AuthenticationConfig convertedAuthnConfig; + if(authnConfig.clientCredentialsConfig().isPresent()) { + Config.OIDCClientCredentialsConfig oidcClientCredentialsConfig = authnConfig.clientCredentialsConfig().get(); + + convertedAuthnConfig = new OIDCClientCredentialsAuthenticationConfig(); + var convertedOidcClientCredentialsConfig = new OIDCClientCredentialsConfig(); + convertedOidcClientCredentialsConfig.setIssuer(oidcClientCredentialsConfig.issuer()); + convertedOidcClientCredentialsConfig.setClientId(oidcClientCredentialsConfig.clientId()); + convertedOidcClientCredentialsConfig.setClientSecret(oidcClientCredentialsConfig.clientSecret()); + convertedOidcClientCredentialsConfig.setScope(oidcClientCredentialsConfig.scope()); + convertedOidcClientCredentialsConfig.setOidcClientCredentialsMinterImplementation(oidcClientCredentialsConfig.oidcClientCredentialsMinterImplementation()); + + ((OIDCClientCredentialsAuthenticationConfig)convertedAuthnConfig).setCredentialsConfig(convertedOidcClientCredentialsConfig); + } else { + convertedAuthnConfig = new AuthenticationConfig(); + } + + convertedAuthnConfig.setMode(authnConfig.mode()); + + return convertedAuthnConfig; + } +} diff --git a/src/main/java/org/project_kessel/relations/client/CDIManagedRelationsClients.java b/src/main/java/org/project_kessel/relations/client/CDIManagedRelationsClients.java index 26ff06a..22b582f 100644 --- a/src/main/java/org/project_kessel/relations/client/CDIManagedRelationsClients.java +++ b/src/main/java/org/project_kessel/relations/client/CDIManagedRelationsClients.java @@ -2,7 +2,7 @@ import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.inject.Produces; -import org.project_kessel.clients.Config; +import org.project_kessel.clients.authn.AuthenticationConfig.AuthMode; /** * A managed bean for providing clients for injection in apps. @@ -16,7 +16,7 @@ public class CDIManagedRelationsClients { RelationsGrpcClientsManager getManager(Config config) { var isSecureClients = config.isSecureClients(); var targetUrl = config.targetUrl(); - var authnEnabled = config.authenticationConfig().map(t -> !t.mode().equals(Config.AuthMode.DISABLED)).orElse(false); + var authnEnabled = config.authenticationConfig().map(t -> !t.mode().equals(AuthMode.DISABLED)).orElse(false); if (isSecureClients) { if(authnEnabled) { diff --git a/src/main/java/org/project_kessel/clients/Config.java b/src/main/java/org/project_kessel/relations/client/Config.java similarity index 91% rename from src/main/java/org/project_kessel/clients/Config.java rename to src/main/java/org/project_kessel/relations/client/Config.java index 0cbaeb3..94bcd45 100644 --- a/src/main/java/org/project_kessel/clients/Config.java +++ b/src/main/java/org/project_kessel/relations/client/Config.java @@ -1,8 +1,9 @@ -package org.project_kessel.clients; +package org.project_kessel.relations.client; import io.smallrye.config.ConfigMapping; import io.smallrye.config.WithDefault; import io.smallrye.config.WithName; +import org.project_kessel.clients.authn.AuthenticationConfig.AuthMode; import java.util.Optional; @@ -14,11 +15,6 @@ */ @ConfigMapping(prefix = "relations-api") public interface Config { - enum AuthMode { - DISABLED, - OIDC_CLIENT_CREDENTIALS - } - @WithDefault("false") boolean isSecureClients(); String targetUrl(); diff --git a/src/main/java/org/project_kessel/relations/client/RelationsGrpcClientsManager.java b/src/main/java/org/project_kessel/relations/client/RelationsGrpcClientsManager.java index 5820878..4bf242d 100644 --- a/src/main/java/org/project_kessel/relations/client/RelationsGrpcClientsManager.java +++ b/src/main/java/org/project_kessel/relations/client/RelationsGrpcClientsManager.java @@ -2,7 +2,6 @@ import io.grpc.Channel; import org.project_kessel.clients.ChannelManager; -import org.project_kessel.clients.Config; import org.project_kessel.clients.KesselClientsManager; public final class RelationsGrpcClientsManager extends KesselClientsManager { @@ -15,7 +14,7 @@ public static RelationsGrpcClientsManager forInsecureClients(String targetUrl) { } public static RelationsGrpcClientsManager forInsecureClients(String targetUrl, Config.AuthenticationConfig authnConfig) throws RuntimeException { - return new RelationsGrpcClientsManager(ChannelManager.instance().forInsecureClients(targetUrl, authnConfig)); + return new RelationsGrpcClientsManager(ChannelManager.instance().forInsecureClients(targetUrl, AuthnConfigConverter.convert(authnConfig))); } public static RelationsGrpcClientsManager forSecureClients(String targetUrl) { @@ -23,7 +22,7 @@ public static RelationsGrpcClientsManager forSecureClients(String targetUrl) { } public static RelationsGrpcClientsManager forSecureClients(String targetUrl, Config.AuthenticationConfig authnConfig) { - return new RelationsGrpcClientsManager(ChannelManager.instance().forSecureClients(targetUrl, authnConfig)); + return new RelationsGrpcClientsManager(ChannelManager.instance().forSecureClients(targetUrl, AuthnConfigConverter.convert(authnConfig))); } public CheckClient getCheckClient() { diff --git a/src/test/java/org/project_kessel/clients/authn/CallCredentialsFactoryTest.java b/src/test/java/org/project_kessel/clients/authn/CallCredentialsFactoryTest.java index f758c04..1034788 100644 --- a/src/test/java/org/project_kessel/clients/authn/CallCredentialsFactoryTest.java +++ b/src/test/java/org/project_kessel/clients/authn/CallCredentialsFactoryTest.java @@ -1,18 +1,16 @@ package org.project_kessel.clients.authn; import org.junit.jupiter.api.Test; -import org.project_kessel.clients.Config; +import org.project_kessel.clients.authn.oidc.client.OIDCClientCredentialsAuthenticationConfig; import java.util.Optional; import static org.junit.jupiter.api.Assertions.fail; -import static org.project_kessel.relations.client.RelationsGrpcClientsManagerTest.dummyAuthConfigWithGoodOIDCClientCredentials; - -class CallCredentialsFactoryTest { +public class CallCredentialsFactoryTest { @Test void testCreateOIDCClientCallCredentials() { - Config.AuthenticationConfig authnConfig = dummyAuthConfigWithGoodOIDCClientCredentials(); + AuthenticationConfig authnConfig = dummyAuthConfigWithGoodOIDCClientCredentials(); try { CallCredentialsFactory.create(authnConfig); } catch (CallCredentialsFactory.CallCredentialsCreationException e) { @@ -22,7 +20,7 @@ void testCreateOIDCClientCallCredentials() { @Test void testFailToCreateCallCredentialsWhenAuthnConfigEmpty() { - Config.AuthenticationConfig authnConfig = null; + AuthenticationConfig authnConfig = null; try { CallCredentialsFactory.create(authnConfig); fail("CallCredentialsFactory creation for OIDC client should throw an exception when OIDC client config is empty."); @@ -32,17 +30,8 @@ void testFailToCreateCallCredentialsWhenAuthnConfigEmpty() { @Test void testFailToCreateCallCredentialsForOIDCWhenConfigEmpty() { - Config.AuthenticationConfig authnConfig = new Config.AuthenticationConfig() { - @Override - public Config.AuthMode mode() { - return Config.AuthMode.OIDC_CLIENT_CREDENTIALS; - } - - @Override - public Optional clientCredentialsConfig() { - return Optional.empty(); - } - }; + var authnConfig = new OIDCClientCredentialsAuthenticationConfig(); + authnConfig.setMode(AuthenticationConfig.AuthMode.OIDC_CLIENT_CREDENTIALS); try { CallCredentialsFactory.create(authnConfig); fail("CallCredentialsFactory creation for OIDC client should throw an exception when OIDC client config is empty."); @@ -50,4 +39,18 @@ public Optional clientCredentialsConfig() { } } + public static OIDCClientCredentialsAuthenticationConfig dummyAuthConfigWithGoodOIDCClientCredentials() { + var oidcClientCredentialsConfig = new OIDCClientCredentialsAuthenticationConfig.OIDCClientCredentialsConfig(); + oidcClientCredentialsConfig.setIssuer("http://localhost:8090"); + oidcClientCredentialsConfig.setClientId("test"); + oidcClientCredentialsConfig.setClientSecret("test"); + oidcClientCredentialsConfig.setScope(Optional.empty()); + oidcClientCredentialsConfig.setOidcClientCredentialsMinterImplementation(Optional.empty()); + + var authnConfig = new OIDCClientCredentialsAuthenticationConfig(); + authnConfig.setMode(AuthenticationConfig.AuthMode.OIDC_CLIENT_CREDENTIALS); + authnConfig.setCredentialsConfig(oidcClientCredentialsConfig); + + return authnConfig; + } } diff --git a/src/test/java/org/project_kessel/clients/authn/oidc/client/OIDCClientCredentialsCallCredentialsTest.java b/src/test/java/org/project_kessel/clients/authn/oidc/client/OIDCClientCredentialsCallCredentialsTest.java index 31b1aca..cc8fdb2 100644 --- a/src/test/java/org/project_kessel/clients/authn/oidc/client/OIDCClientCredentialsCallCredentialsTest.java +++ b/src/test/java/org/project_kessel/clients/authn/oidc/client/OIDCClientCredentialsCallCredentialsTest.java @@ -5,9 +5,7 @@ import io.grpc.Status; import io.grpc.netty.shaded.io.netty.util.concurrent.DefaultEventExecutor; import org.junit.jupiter.api.Test; -import org.project_kessel.clients.Config; -import org.project_kessel.clients.authn.oidc.client.OIDCClientCredentialsCallCredentials; -import org.project_kessel.clients.authn.oidc.client.OIDCClientCredentialsMinter; +import org.project_kessel.clients.authn.AuthenticationConfig; import java.time.LocalDateTime; import java.util.Optional; @@ -100,7 +98,7 @@ void unspecifiedMinterShouldUseDefaultAndNotThrowException() { void shouldApplyBearerMetadata() throws InterruptedException { var authConfig = makeAuthConfig("some", "some", "some", Optional.empty(), Optional.empty()); - var oidcClientCredentialsConfig = authConfig.clientCredentialsConfig().orElse(null); + var oidcClientCredentialsConfig = authConfig.clientCredentialsConfig(); var minter = makeFakeMinter(true, 0); var callCreds = new OIDCClientCredentialsCallCredentials(oidcClientCredentialsConfig, minter); final AtomicReference metaDataRef = new AtomicReference<>(); @@ -119,7 +117,7 @@ void shouldApplyBearerMetadata() throws InterruptedException { void shouldApplyPreviouslyObtainedTokenWhenInLifetime() throws InterruptedException { var authConfig = makeAuthConfig("some", "some", "some", Optional.empty(), Optional.empty()); - var oidcClientCredentialsConfig = authConfig.clientCredentialsConfig().orElse(null); + var oidcClientCredentialsConfig = authConfig.clientCredentialsConfig(); var minter = makeFakeMinter(true, 100000); // big lifetime var callCreds = new OIDCClientCredentialsCallCredentials(oidcClientCredentialsConfig, minter); final AtomicReference metaDataRef = new AtomicReference<>(); @@ -145,7 +143,7 @@ void shouldApplyPreviouslyObtainedTokenWhenInLifetime() throws InterruptedExcept void shouldApplyNewTokenWhenOutOfLifetime() throws InterruptedException { var authConfig = makeAuthConfig("some", "some", "some", Optional.empty(), Optional.empty()); - var oidcClientCredentialsConfig = authConfig.clientCredentialsConfig().orElse(null); + var oidcClientCredentialsConfig = authConfig.clientCredentialsConfig(); var minter = makeFakeMinter(true, 0); // zero lifetime forces new auth token var callCreds = new OIDCClientCredentialsCallCredentials(oidcClientCredentialsConfig, minter); final AtomicReference metaDataRef = new AtomicReference<>(); @@ -171,7 +169,7 @@ void shouldApplyNewTokenWhenOutOfLifetime() throws InterruptedException { void shouldApplyUnauthenticatedWhenAuthnFails() throws InterruptedException { var authConfig = makeAuthConfig("some", "some", "some", Optional.empty(), Optional.empty()); - var oidcClientCredentialsConfig = authConfig.clientCredentialsConfig().orElse(null); + var oidcClientCredentialsConfig = authConfig.clientCredentialsConfig(); var minter = makeFakeMinter(false, 0); var callCreds = new OIDCClientCredentialsCallCredentials(oidcClientCredentialsConfig, minter); final AtomicReference metaDataRef = new AtomicReference<>(); @@ -191,7 +189,7 @@ static OIDCClientCredentialsMinter makeFakeMinter(boolean alwaysSucceedsOrFails, int mintedNumber = 0; @Override - public BearerHeader authenticateAndRetrieveAuthorizationHeader(Config.OIDCClientCredentialsConfig clientConfig) throws OIDCClientCredentialsMinterException { + public BearerHeader authenticateAndRetrieveAuthorizationHeader(OIDCClientCredentialsAuthenticationConfig.OIDCClientCredentialsConfig clientConfig) throws OIDCClientCredentialsMinterException { if (!alwaysSucceedsOrFails) { throw new OIDCClientCredentialsMinterException("Authentication failed."); } @@ -218,46 +216,22 @@ public void fail(Status status) { }; } - public static Config.AuthenticationConfig makeAuthConfig(String issuer, String clientId, String clientSecret) { + public static OIDCClientCredentialsAuthenticationConfig makeAuthConfig(String issuer, String clientId, String clientSecret) { return makeAuthConfig(issuer, clientId, clientSecret, Optional.empty(), Optional.empty()); } - public static Config.AuthenticationConfig makeAuthConfig(String issuer, String clientId, String clientSecret, Optional scope, Optional minterImpl) { - return new Config.AuthenticationConfig() { - @Override - public Config.AuthMode mode() { - return null; - } + public static OIDCClientCredentialsAuthenticationConfig makeAuthConfig(String issuer, String clientId, String clientSecret, Optional scope, Optional minterImpl) { + var oidcClientCredentialsConfig = new OIDCClientCredentialsAuthenticationConfig.OIDCClientCredentialsConfig(); + oidcClientCredentialsConfig.setIssuer(issuer); + oidcClientCredentialsConfig.setClientId(clientId); + oidcClientCredentialsConfig.setClientSecret(clientSecret); + oidcClientCredentialsConfig.setScope(scope); + oidcClientCredentialsConfig.setOidcClientCredentialsMinterImplementation(minterImpl); - @Override - public Optional clientCredentialsConfig() { - return Optional.of(new Config.OIDCClientCredentialsConfig() { - @Override - public String issuer() { - return issuer; - } - - @Override - public String clientId() { - return clientId; - } - - @Override - public String clientSecret() { - return clientSecret; - } - - @Override - public Optional scope() { - return scope; - } - - @Override - public Optional oidcClientCredentialsMinterImplementation() { - return minterImpl; - } - }); - } - }; + var authnConfig = new OIDCClientCredentialsAuthenticationConfig(); + authnConfig.setMode(null); + authnConfig.setCredentialsConfig(oidcClientCredentialsConfig); + + return authnConfig; } } diff --git a/src/test/java/org/project_kessel/clients/authn/oidc/client/OIDCClientCredentialsMinterTest.java b/src/test/java/org/project_kessel/clients/authn/oidc/client/OIDCClientCredentialsMinterTest.java index 50564ae..855bb98 100644 --- a/src/test/java/org/project_kessel/clients/authn/oidc/client/OIDCClientCredentialsMinterTest.java +++ b/src/test/java/org/project_kessel/clients/authn/oidc/client/OIDCClientCredentialsMinterTest.java @@ -1,8 +1,6 @@ package org.project_kessel.clients.authn.oidc.client; import org.junit.jupiter.api.Test; -import org.project_kessel.clients.Config; -import org.project_kessel.clients.authn.oidc.client.OIDCClientCredentialsMinter; import java.time.LocalDateTime; import java.util.Optional; @@ -91,7 +89,7 @@ public TestMinter() { } @Override - public BearerHeader authenticateAndRetrieveAuthorizationHeader(Config.OIDCClientCredentialsConfig clientConfig) throws OIDCClientCredentialsMinterException { + public BearerHeader authenticateAndRetrieveAuthorizationHeader(OIDCClientCredentialsAuthenticationConfig.OIDCClientCredentialsConfig clientConfig) throws OIDCClientCredentialsMinterException { return null; } } diff --git a/src/test/java/org/project_kessel/clients/authn/oidc/client/nimbus/NimbusOIDCClientCredentialsMinterTest.java b/src/test/java/org/project_kessel/clients/authn/oidc/client/nimbus/NimbusOIDCClientCredentialsMinterTest.java index 068eaf6..8ec2614 100644 --- a/src/test/java/org/project_kessel/clients/authn/oidc/client/nimbus/NimbusOIDCClientCredentialsMinterTest.java +++ b/src/test/java/org/project_kessel/clients/authn/oidc/client/nimbus/NimbusOIDCClientCredentialsMinterTest.java @@ -1,7 +1,7 @@ package org.project_kessel.clients.authn.oidc.client.nimbus; import org.junit.jupiter.api.Test; -import org.project_kessel.relations.client.RelationsGrpcClientsManagerTest; +import org.project_kessel.clients.authn.CallCredentialsFactoryTest; import org.project_kessel.clients.authn.oidc.client.OIDCClientCredentialsMinter; import org.project_kessel.clients.fake.FakeIdp; @@ -12,12 +12,12 @@ public class NimbusOIDCClientCredentialsMinterTest { @Test void shouldReturnBearerHeaderWhenIdPAuthenticates() { var minter = new NimbusOIDCClientCredentialsMinter(); - var config = RelationsGrpcClientsManagerTest.dummyAuthConfigWithGoodOIDCClientCredentials().clientCredentialsConfig(); + var config = CallCredentialsFactoryTest.dummyAuthConfigWithGoodOIDCClientCredentials().clientCredentialsConfig(); OIDCClientCredentialsMinter.BearerHeader bearerHeader = null; try { FakeIdp fakeIdp = new FakeIdp(8090); fakeIdp.start(); - bearerHeader = minter.authenticateAndRetrieveAuthorizationHeader(config.get()); + bearerHeader = minter.authenticateAndRetrieveAuthorizationHeader(config); fakeIdp.stop(); } catch (OIDCClientCredentialsMinter.OIDCClientCredentialsMinterException e) { fail("Should not throw exception if authn is successful."); @@ -30,11 +30,11 @@ void shouldReturnBearerHeaderWhenIdPAuthenticates() { @Test void shouldThrowExceptionWhenIdPAuthenticationFails() { var minter = new NimbusOIDCClientCredentialsMinter(); - var config = RelationsGrpcClientsManagerTest.dummyAuthConfigWithGoodOIDCClientCredentials().clientCredentialsConfig(); + var config = CallCredentialsFactoryTest.dummyAuthConfigWithGoodOIDCClientCredentials().clientCredentialsConfig(); FakeIdp fakeIdp = new FakeIdp(8090, false); try { fakeIdp.start(); - minter.authenticateAndRetrieveAuthorizationHeader(config.get()); + minter.authenticateAndRetrieveAuthorizationHeader(config); fail("Should throw exception if authn is not successful."); } catch (OIDCClientCredentialsMinter.OIDCClientCredentialsMinterException e) { // success diff --git a/src/test/java/org/project_kessel/relations/client/util/CertUtil.java b/src/test/java/org/project_kessel/clients/util/CertUtil.java similarity index 98% rename from src/test/java/org/project_kessel/relations/client/util/CertUtil.java rename to src/test/java/org/project_kessel/clients/util/CertUtil.java index e3c9bd4..83e0bf5 100644 --- a/src/test/java/org/project_kessel/relations/client/util/CertUtil.java +++ b/src/test/java/org/project_kessel/clients/util/CertUtil.java @@ -1,4 +1,4 @@ -package org.project_kessel.relations.client.util; +package org.project_kessel.clients.util; import java.io.*; import java.security.*; diff --git a/src/test/java/org/project_kessel/relations/client/CDIManagedRelationsClientsContainerTests.java b/src/test/java/org/project_kessel/relations/client/CDIManagedRelationsClientsContainerTests.java index 13bb8f0..f9164ed 100644 --- a/src/test/java/org/project_kessel/relations/client/CDIManagedRelationsClientsContainerTests.java +++ b/src/test/java/org/project_kessel/relations/client/CDIManagedRelationsClientsContainerTests.java @@ -13,7 +13,6 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.project_kessel.clients.Config; import java.io.IOException; import java.util.Optional; diff --git a/src/test/java/org/project_kessel/relations/client/CDIManagedRelationsClientsTest.java b/src/test/java/org/project_kessel/relations/client/CDIManagedRelationsClientsTest.java index d5f4e1a..0cdbe42 100644 --- a/src/test/java/org/project_kessel/relations/client/CDIManagedRelationsClientsTest.java +++ b/src/test/java/org/project_kessel/relations/client/CDIManagedRelationsClientsTest.java @@ -3,7 +3,7 @@ import org.junit.jupiter.api.Test; import org.mockito.MockedStatic; import org.mockito.Mockito; -import org.project_kessel.clients.Config; +import org.project_kessel.clients.authn.AuthenticationConfig; import java.util.Optional; @@ -136,12 +136,12 @@ public Optional authenticationConfig() { static Config.AuthenticationConfig makeDummyAuthenticationConfig(boolean authnEnabled) { return new Config.AuthenticationConfig() { @Override - public Config.AuthMode mode() { + public AuthenticationConfig.AuthMode mode() { if(!authnEnabled) { - return Config.AuthMode.DISABLED; + return AuthenticationConfig.AuthMode.DISABLED; } // pick some arbitrary non disabled mode - return Config.AuthMode.OIDC_CLIENT_CREDENTIALS; + return AuthenticationConfig.AuthMode.OIDC_CLIENT_CREDENTIALS; } @Override diff --git a/src/test/java/org/project_kessel/clients/ConfigTest.java b/src/test/java/org/project_kessel/relations/client/ConfigTest.java similarity index 96% rename from src/test/java/org/project_kessel/clients/ConfigTest.java rename to src/test/java/org/project_kessel/relations/client/ConfigTest.java index 834b517..5a2f67b 100644 --- a/src/test/java/org/project_kessel/clients/ConfigTest.java +++ b/src/test/java/org/project_kessel/relations/client/ConfigTest.java @@ -1,4 +1,4 @@ -package org.project_kessel.clients; +package org.project_kessel.relations.client; import io.smallrye.config.SmallRyeConfigBuilder; diff --git a/src/test/java/org/project_kessel/relations/client/RelationsGrpcClientsManagerTest.java b/src/test/java/org/project_kessel/relations/client/RelationsGrpcClientsManagerTest.java index 397bba3..086ef33 100644 --- a/src/test/java/org/project_kessel/relations/client/RelationsGrpcClientsManagerTest.java +++ b/src/test/java/org/project_kessel/relations/client/RelationsGrpcClientsManagerTest.java @@ -10,8 +10,10 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.project_kessel.clients.ChannelManager; -import org.project_kessel.clients.Config; import org.project_kessel.clients.KesselClient; +import org.project_kessel.clients.authn.AuthenticationConfig; +import org.project_kessel.clients.authn.CallCredentialsFactoryTest; +import org.project_kessel.clients.authn.oidc.client.OIDCClientCredentialsAuthenticationConfig; import org.project_kessel.clients.fake.GrpcServerSpy; import java.util.HashMap; @@ -23,7 +25,7 @@ import static io.smallrye.common.constraint.Assert.assertNotNull; import static org.junit.jupiter.api.Assertions.*; -import static org.project_kessel.relations.client.util.CertUtil.*; +import static org.project_kessel.clients.util.CertUtil.*; public class RelationsGrpcClientsManagerTest { private static final Metadata.Key authorizationKey = Metadata.Key.of("Authorization", Metadata.ASCII_STRING_MARSHALLER); @@ -254,8 +256,8 @@ void testCreateAndShutdownPatternsInternal() throws Exception { public static Config.AuthenticationConfig dummyAuthConfigWithGoodOIDCClientCredentials() { return new Config.AuthenticationConfig() { @Override - public Config.AuthMode mode() { - return Config.AuthMode.OIDC_CLIENT_CREDENTIALS; // any non-disabled value + public AuthenticationConfig.AuthMode mode() { + return AuthenticationConfig.AuthMode.OIDC_CLIENT_CREDENTIALS; // any non-disabled value } @Override From 052dd63a4acad4d0e4ef25d5c36a55bf9f61bbc7 Mon Sep 17 00:00:00 2001 From: Mark McLaughlin Date: Thu, 22 Aug 2024 14:27:35 +0100 Subject: [PATCH 06/13] Allow separate clients to use separate ChannelManagers (by default). --- .../clients/ChannelManager.java | 14 +++++++---- .../clients/KesselClientsManager.java | 8 ------- .../client/RelationsGrpcClientsManager.java | 18 ++++++++++---- .../clients/ChannelManagerTest.java | 24 +++++++++++++++++++ .../RelationsGrpcClientsManagerTest.java | 15 ++++++++---- 5 files changed, 58 insertions(+), 21 deletions(-) create mode 100644 src/test/java/org/project_kessel/clients/ChannelManagerTest.java diff --git a/src/main/java/org/project_kessel/clients/ChannelManager.java b/src/main/java/org/project_kessel/clients/ChannelManager.java index 70dfe02..bda42a0 100644 --- a/src/main/java/org/project_kessel/clients/ChannelManager.java +++ b/src/main/java/org/project_kessel/clients/ChannelManager.java @@ -5,19 +5,25 @@ import org.project_kessel.clients.authn.CallCredentialsFactory; import java.util.HashMap; +import java.util.Hashtable; public final class ChannelManager { private static ChannelManager channelManager; + /* In the scenario where multiple clients are using this library, we need to support multiple channel managers, + * so that clients can use separate channels. There may also be scenarios where reusing the same channel manager, and + * hence channels, may be desirable. */ + private static final Hashtable channelManagers = new Hashtable<>(); + private final HashMap insecureManagers = new HashMap<>(); private final HashMap secureManagers = new HashMap<>(); - public static ChannelManager instance() { - if(channelManager == null) { - channelManager = new ChannelManager(); + public static ChannelManager getInstance(String channelManagerKey) { + if(!channelManagers.containsKey(channelManagerKey)) { + channelManagers.put(channelManagerKey, new ChannelManager()); } - return channelManager; + return channelManagers.get(channelManagerKey); } public synchronized Channel forInsecureClients(String targetUrl) { diff --git a/src/main/java/org/project_kessel/clients/KesselClientsManager.java b/src/main/java/org/project_kessel/clients/KesselClientsManager.java index f6c17bb..17f3d59 100644 --- a/src/main/java/org/project_kessel/clients/KesselClientsManager.java +++ b/src/main/java/org/project_kessel/clients/KesselClientsManager.java @@ -9,14 +9,6 @@ protected KesselClientsManager(Channel channel) { this.channel = channel; } - public static void shutdownAll() { - ChannelManager.instance().shutdownAll(); - } - - public static void shutdownManager(KesselClientsManager managerToShutdown) { - ChannelManager.instance().shutdownChannel(managerToShutdown.channel); - } - @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/src/main/java/org/project_kessel/relations/client/RelationsGrpcClientsManager.java b/src/main/java/org/project_kessel/relations/client/RelationsGrpcClientsManager.java index 4bf242d..a4229ff 100644 --- a/src/main/java/org/project_kessel/relations/client/RelationsGrpcClientsManager.java +++ b/src/main/java/org/project_kessel/relations/client/RelationsGrpcClientsManager.java @@ -9,20 +9,30 @@ private RelationsGrpcClientsManager(Channel channel) { super(channel); } + private static final String CHANNEL_MANAGER_KEY = RelationsGrpcClientsManager.class.getName(); + public static RelationsGrpcClientsManager forInsecureClients(String targetUrl) { - return new RelationsGrpcClientsManager(ChannelManager.instance().forInsecureClients(targetUrl)); + return new RelationsGrpcClientsManager(ChannelManager.getInstance(CHANNEL_MANAGER_KEY).forInsecureClients(targetUrl)); } public static RelationsGrpcClientsManager forInsecureClients(String targetUrl, Config.AuthenticationConfig authnConfig) throws RuntimeException { - return new RelationsGrpcClientsManager(ChannelManager.instance().forInsecureClients(targetUrl, AuthnConfigConverter.convert(authnConfig))); + return new RelationsGrpcClientsManager(ChannelManager.getInstance(CHANNEL_MANAGER_KEY).forInsecureClients(targetUrl, AuthnConfigConverter.convert(authnConfig))); } public static RelationsGrpcClientsManager forSecureClients(String targetUrl) { - return new RelationsGrpcClientsManager(ChannelManager.instance().forSecureClients(targetUrl)); + return new RelationsGrpcClientsManager(ChannelManager.getInstance(CHANNEL_MANAGER_KEY).forSecureClients(targetUrl)); } public static RelationsGrpcClientsManager forSecureClients(String targetUrl, Config.AuthenticationConfig authnConfig) { - return new RelationsGrpcClientsManager(ChannelManager.instance().forSecureClients(targetUrl, AuthnConfigConverter.convert(authnConfig))); + return new RelationsGrpcClientsManager(ChannelManager.getInstance(CHANNEL_MANAGER_KEY).forSecureClients(targetUrl, AuthnConfigConverter.convert(authnConfig))); + } + + public static void shutdownAll() { + ChannelManager.getInstance(CHANNEL_MANAGER_KEY).shutdownAll(); + } + + public static void shutdownManager(RelationsGrpcClientsManager managerToShutdown) { + ChannelManager.getInstance(CHANNEL_MANAGER_KEY).shutdownChannel(managerToShutdown.channel); } public CheckClient getCheckClient() { diff --git a/src/test/java/org/project_kessel/clients/ChannelManagerTest.java b/src/test/java/org/project_kessel/clients/ChannelManagerTest.java new file mode 100644 index 0000000..e2a5e26 --- /dev/null +++ b/src/test/java/org/project_kessel/clients/ChannelManagerTest.java @@ -0,0 +1,24 @@ +package org.project_kessel.clients; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +public class ChannelManagerTest { + @Test + void shouldSelectSameManager() { + ChannelManager channelManager = ChannelManager.getInstance("myChannelManangerKey"); + ChannelManager channelManager2 = ChannelManager.getInstance("myChannelManangerKey"); + + assertEquals(channelManager, channelManager2); + } + + @Test + void shouldNotSelectSameManager() { + ChannelManager channelManager = ChannelManager.getInstance("myChannelManangerKey"); + ChannelManager channelManager2 = ChannelManager.getInstance("anotherChannelManagerKey"); + + assertNotEquals(channelManager, channelManager2); + } +} diff --git a/src/test/java/org/project_kessel/relations/client/RelationsGrpcClientsManagerTest.java b/src/test/java/org/project_kessel/relations/client/RelationsGrpcClientsManagerTest.java index 086ef33..4691b8f 100644 --- a/src/test/java/org/project_kessel/relations/client/RelationsGrpcClientsManagerTest.java +++ b/src/test/java/org/project_kessel/relations/client/RelationsGrpcClientsManagerTest.java @@ -12,8 +12,6 @@ import org.project_kessel.clients.ChannelManager; import org.project_kessel.clients.KesselClient; import org.project_kessel.clients.authn.AuthenticationConfig; -import org.project_kessel.clients.authn.CallCredentialsFactoryTest; -import org.project_kessel.clients.authn.oidc.client.OIDCClientCredentialsAuthenticationConfig; import org.project_kessel.clients.fake.GrpcServerSpy; import java.util.HashMap; @@ -171,7 +169,7 @@ void testManagersHoldIntendedCredentialsInChannel() throws Exception { } /* - Tests relying on reflection. Maybe be brittle and could be removed in future. + Tests relying on reflection. Brittle and could be removed in future. */ @Test @@ -183,7 +181,10 @@ void testManagerReuseInternal() throws Exception { RelationsGrpcClientsManager.forSecureClients("localhost1:8080"); RelationsGrpcClientsManager.forSecureClients("localhost1:8080"); // same as five - var kesselChannelManager = ChannelManager.instance(); + var channelManagerKeyField = RelationsGrpcClientsManager.class.getDeclaredField("CHANNEL_MANAGER_KEY"); + channelManagerKeyField.setAccessible(true); + var channelManagerKey = (String)channelManagerKeyField.get(null); + var kesselChannelManager = ChannelManager.getInstance(channelManagerKey); var insecureField = ChannelManager.class.getDeclaredField("insecureManagers"); insecureField.setAccessible(true); @@ -219,7 +220,11 @@ void testSameChannelUsedByClientsInternal() throws Exception { @Test void testCreateAndShutdownPatternsInternal() throws Exception { - var kesselChannelManager = ChannelManager.instance(); + var channelManagerKeyField = RelationsGrpcClientsManager.class.getDeclaredField("CHANNEL_MANAGER_KEY"); + channelManagerKeyField.setAccessible(true); + var channelManagerKey = (String)channelManagerKeyField.get(null); + var kesselChannelManager = ChannelManager.getInstance(channelManagerKey); + var insecureField = ChannelManager.class.getDeclaredField("insecureManagers"); insecureField.setAccessible(true); var insecureManagersSize = ((HashMap)insecureField.get(kesselChannelManager)).size(); From a55e94ccc94598c6fcafe65816e384018a85fc45 Mon Sep 17 00:00:00 2001 From: Mark McLaughlin Date: Thu, 22 Aug 2024 16:08:09 +0100 Subject: [PATCH 07/13] Move generic tests from RelationsGrpcClientsManagerTest to ChannelManagerTest. --- .../clients/KesselClientsManager.java | 14 -- .../clients/ChannelManagerTest.java | 166 +++++++++++++++++- .../RelationsGrpcClientsManagerTest.java | 156 ---------------- 3 files changed, 163 insertions(+), 173 deletions(-) diff --git a/src/main/java/org/project_kessel/clients/KesselClientsManager.java b/src/main/java/org/project_kessel/clients/KesselClientsManager.java index 17f3d59..d79f4e4 100644 --- a/src/main/java/org/project_kessel/clients/KesselClientsManager.java +++ b/src/main/java/org/project_kessel/clients/KesselClientsManager.java @@ -8,18 +8,4 @@ public abstract class KesselClientsManager { protected KesselClientsManager(Channel channel) { this.channel = channel; } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - KesselClientsManager that = (KesselClientsManager) o; - return channel.equals(that.channel); - } - - @Override - public int hashCode() { - return channel.hashCode(); - } } diff --git a/src/test/java/org/project_kessel/clients/ChannelManagerTest.java b/src/test/java/org/project_kessel/clients/ChannelManagerTest.java index e2a5e26..de15e00 100644 --- a/src/test/java/org/project_kessel/clients/ChannelManagerTest.java +++ b/src/test/java/org/project_kessel/clients/ChannelManagerTest.java @@ -1,11 +1,27 @@ package org.project_kessel.clients; +import io.grpc.Channel; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import static io.smallrye.common.constraint.Assert.assertNotNull; +import static org.junit.jupiter.api.Assertions.*; + +class ChannelManagerTest { + ChannelManager defaultChannelManager = ChannelManager.getInstance("defaultChannelManager"); + + @AfterEach + void testTeardown() { + /* Make sure all client managers shutdown/removed after each test */ + defaultChannelManager.shutdownAll(); + } -public class ChannelManagerTest { @Test void shouldSelectSameManager() { ChannelManager channelManager = ChannelManager.getInstance("myChannelManangerKey"); @@ -21,4 +37,148 @@ void shouldNotSelectSameManager() { assertNotEquals(channelManager, channelManager2); } + + @Test + void testManagerChannelReusePatterns() { + var one = defaultChannelManager.forInsecureClients("localhost:8080"); + var two = defaultChannelManager.forInsecureClients("localhost:8080"); // same as one + var three = defaultChannelManager.forInsecureClients("localhost1:8080"); + var four = defaultChannelManager.forSecureClients("localhost:8080"); + var five = defaultChannelManager.forSecureClients("localhost1:8080"); + var six = defaultChannelManager.forSecureClients("localhost1:8080"); // same as five + + assertNotNull(one); + assertNotNull(two); + assertNotNull(three); + assertNotNull(four); + assertNotNull(five); + assertNotNull(six); + assertEquals(one, two); + assertNotEquals(two, three); + assertEquals(five, six); + assertNotEquals(four, five); + } + + @Test + void testThreadingChaos() { + /* Basic testing to ensure that we don't get ConcurrentModificationExceptions, or any other exceptions, when + * creating and destroying managers on different threads. */ + + try { + Hashtable channels = new Hashtable<>(); + + int numberOfThreads = 100; + ExecutorService service = Executors.newFixedThreadPool(numberOfThreads); + CountDownLatch latch1 = new CountDownLatch(numberOfThreads / 3); + CountDownLatch latch2 = new CountDownLatch(numberOfThreads * 2 / 3 - numberOfThreads / 3); + CountDownLatch latch3 = new CountDownLatch(numberOfThreads - numberOfThreads * 2 / 3); + + /* A: Use 1/3 of threads to request/create channels at the same time. */ + for (int i = 0; i < numberOfThreads / 3; i++) { + final int j = i; + service.submit(() -> { + Channel manager; + if(j % 2 == 0) { + manager = defaultChannelManager.forInsecureClients("localhost" + j); + } else { + manager = defaultChannelManager.forSecureClients("localhost" + j); + } + channels.put("localhost" + j, manager); + + latch1.countDown(); + }); + } + + latch1.await(); + /* B and C, below, trigger at the same time once A is done. */ + + /* B: Use 1/3 of threads to shut down the above created channels. */ + for (int i = numberOfThreads / 3; i < numberOfThreads * 2 / 3; i++) { + final int j = i - numberOfThreads / 3; + service.submit(() -> { + defaultChannelManager.shutdownChannel(channels.get("localhost" + j)); + latch2.countDown(); + }); + } + + /* C: Use 1/3 of the threads to recreate/retrieve the same channels at the same time as B. */ + for (int i = numberOfThreads * 2 / 3; i < numberOfThreads; i++) { + final int j = i - numberOfThreads * 2 / 3; + service.submit(() -> { + Channel manager; + if(j % 2 == 0) { + manager = defaultChannelManager.forInsecureClients("localhost" + j); + } else { + manager = defaultChannelManager.forSecureClients("localhost" + j); + } + channels.put("localhost" + j, manager); + + latch3.countDown(); + }); + } + latch2.await(); + latch3.await(); + } catch(Exception e) { + fail("Should not have thrown any exception"); + } + } + + /* + Tests relying on reflection. Brittle and could be removed in future. + */ + + @Test + void testManagerReuseInternal() throws Exception { + defaultChannelManager.forInsecureClients("localhost:8080"); + defaultChannelManager.forInsecureClients("localhost:8080"); // same as one + defaultChannelManager.forInsecureClients("localhost1:8080"); + defaultChannelManager.forSecureClients("localhost:8080"); + defaultChannelManager.forSecureClients("localhost1:8080"); + defaultChannelManager.forSecureClients("localhost1:8080"); // same as five + + var insecureField = ChannelManager.class.getDeclaredField("insecureManagers"); + insecureField.setAccessible(true); + var secureField = ChannelManager.class.getDeclaredField("secureManagers"); + secureField.setAccessible(true); + var insecureManagers = (HashMap)insecureField.get(defaultChannelManager); + var secureManagers = (HashMap)secureField.get(defaultChannelManager); + + assertEquals(2, insecureManagers.size()); + assertEquals(2, secureManagers.size()); + } + + @Test + void testCreateAndShutdownPatternsInternal() throws Exception { + var insecureField = ChannelManager.class.getDeclaredField("insecureManagers"); + insecureField.setAccessible(true); + var insecureManagersSize = ((HashMap)insecureField.get(defaultChannelManager)).size(); + + assertEquals(0, insecureManagersSize); + + var channel = defaultChannelManager.forInsecureClients("localhost:8080"); + insecureManagersSize = ((HashMap)insecureField.get(defaultChannelManager)).size(); + assertEquals(1, insecureManagersSize); + + defaultChannelManager.shutdownChannel(channel); + insecureManagersSize = ((HashMap)insecureField.get(defaultChannelManager)).size(); + assertEquals(0, insecureManagersSize); + + /* Shouldn't throw exception if executed twice */ + defaultChannelManager.shutdownChannel(channel); + insecureManagersSize = ((HashMap)insecureField.get(defaultChannelManager)).size(); + assertEquals(0, insecureManagersSize); + + var manager2 = defaultChannelManager.forInsecureClients("localhost:8080"); + insecureManagersSize = ((HashMap)insecureField.get(defaultChannelManager)).size(); + assertEquals(1, insecureManagersSize); + assertNotEquals(channel, manager2); + + defaultChannelManager.forInsecureClients("localhost:8081"); + insecureManagersSize = ((HashMap)insecureField.get(defaultChannelManager)).size(); + assertEquals(2, insecureManagersSize); + + defaultChannelManager.shutdownAll(); + insecureManagersSize = ((HashMap)insecureField.get(defaultChannelManager)).size(); + assertEquals(0, insecureManagersSize); + } } diff --git a/src/test/java/org/project_kessel/relations/client/RelationsGrpcClientsManagerTest.java b/src/test/java/org/project_kessel/relations/client/RelationsGrpcClientsManagerTest.java index 4691b8f..9eaca41 100644 --- a/src/test/java/org/project_kessel/relations/client/RelationsGrpcClientsManagerTest.java +++ b/src/test/java/org/project_kessel/relations/client/RelationsGrpcClientsManagerTest.java @@ -9,17 +9,11 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.project_kessel.clients.ChannelManager; import org.project_kessel.clients.KesselClient; import org.project_kessel.clients.authn.AuthenticationConfig; import org.project_kessel.clients.fake.GrpcServerSpy; -import java.util.HashMap; -import java.util.Hashtable; import java.util.Optional; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import static io.smallrye.common.constraint.Assert.assertNotNull; import static org.junit.jupiter.api.Assertions.*; @@ -48,91 +42,6 @@ static void removeTestSetup() { removeTestCACertFromKeystore(); } - @Test - void testManagerChannelReusePatterns() { - var one = RelationsGrpcClientsManager.forInsecureClients("localhost:8080"); - var two = RelationsGrpcClientsManager.forInsecureClients("localhost:8080"); // same as one - var three = RelationsGrpcClientsManager.forInsecureClients("localhost1:8080"); - var four = RelationsGrpcClientsManager.forSecureClients("localhost:8080"); - var five = RelationsGrpcClientsManager.forSecureClients("localhost1:8080"); - var six = RelationsGrpcClientsManager.forSecureClients("localhost1:8080"); // same as five - - assertNotNull(one); - assertNotNull(two); - assertNotNull(three); - assertNotNull(four); - assertNotNull(five); - assertNotNull(six); - assertEquals(one, two); - assertNotEquals(two, three); - assertEquals(five, six); - assertNotEquals(four, five); - } - - @Test - void testThreadingChaos() { - /* Basic testing to ensure that we don't get ConcurrentModificationExceptions, or any other exceptions, when - * creating and destroying managers on different threads. */ - - try { - Hashtable managers = new Hashtable<>(); - - int numberOfThreads = 100; - ExecutorService service = Executors.newFixedThreadPool(numberOfThreads); - CountDownLatch latch1 = new CountDownLatch(numberOfThreads / 3); - CountDownLatch latch2 = new CountDownLatch(numberOfThreads * 2 / 3 - numberOfThreads / 3); - CountDownLatch latch3 = new CountDownLatch(numberOfThreads - numberOfThreads * 2 / 3); - - /* A: Use 1/3 of threads to request/create managers at the same time. */ - for (int i = 0; i < numberOfThreads / 3; i++) { - final int j = i; - service.submit(() -> { - RelationsGrpcClientsManager manager; - if(j % 2 == 0) { - manager = RelationsGrpcClientsManager.forInsecureClients("localhost" + j); - } else { - manager = RelationsGrpcClientsManager.forSecureClients("localhost" + j); - } - managers.put("localhost" + j, manager); - - latch1.countDown(); - }); - } - - latch1.await(); - /* B and C, below, trigger at the same time once A is done. */ - - /* B: Use 1/3 of threads to shut down the above created managers. */ - for (int i = numberOfThreads / 3; i < numberOfThreads * 2 / 3; i++) { - final int j = i - numberOfThreads / 3; - service.submit(() -> { - RelationsGrpcClientsManager.shutdownManager(managers.get("localhost" + j)); - latch2.countDown(); - }); - } - - /* C: Use 1/3 of the threads to recreate/retrieve the same managers at the same time as B. */ - for (int i = numberOfThreads * 2 / 3; i < numberOfThreads; i++) { - final int j = i - numberOfThreads * 2 / 3; - service.submit(() -> { - RelationsGrpcClientsManager manager; - if(j % 2 == 0) { - manager = RelationsGrpcClientsManager.forInsecureClients("localhost" + j); - } else { - manager = RelationsGrpcClientsManager.forSecureClients("localhost" + j); - } - managers.put("localhost" + j, manager); - - latch3.countDown(); - }); - } - latch2.await(); - latch3.await(); - } catch(Exception e) { - fail("Should not have thrown any exception"); - } - } - /* End-to-end tests against fake IdP and/or fake grpc relations-api */ @@ -172,31 +81,6 @@ void testManagersHoldIntendedCredentialsInChannel() throws Exception { Tests relying on reflection. Brittle and could be removed in future. */ - @Test - void testManagerReuseInternal() throws Exception { - RelationsGrpcClientsManager.forInsecureClients("localhost:8080"); - RelationsGrpcClientsManager.forInsecureClients("localhost:8080"); // same as one - RelationsGrpcClientsManager.forInsecureClients("localhost1:8080"); - RelationsGrpcClientsManager.forSecureClients("localhost:8080"); - RelationsGrpcClientsManager.forSecureClients("localhost1:8080"); - RelationsGrpcClientsManager.forSecureClients("localhost1:8080"); // same as five - - var channelManagerKeyField = RelationsGrpcClientsManager.class.getDeclaredField("CHANNEL_MANAGER_KEY"); - channelManagerKeyField.setAccessible(true); - var channelManagerKey = (String)channelManagerKeyField.get(null); - var kesselChannelManager = ChannelManager.getInstance(channelManagerKey); - - var insecureField = ChannelManager.class.getDeclaredField("insecureManagers"); - insecureField.setAccessible(true); - var secureField = ChannelManager.class.getDeclaredField("secureManagers"); - secureField.setAccessible(true); - var insecureManagers = (HashMap)insecureField.get(kesselChannelManager); - var secureManagers = (HashMap)secureField.get(kesselChannelManager); - - assertEquals(2, insecureManagers.size()); - assertEquals(2, secureManagers.size()); - } - @Test void testSameChannelUsedByClientsInternal() throws Exception { var manager = RelationsGrpcClientsManager.forInsecureClients("localhost:8080"); @@ -218,46 +102,6 @@ void testSameChannelUsedByClientsInternal() throws Exception { assertEquals(lookupChannel, relationTuplesChannel); } - @Test - void testCreateAndShutdownPatternsInternal() throws Exception { - var channelManagerKeyField = RelationsGrpcClientsManager.class.getDeclaredField("CHANNEL_MANAGER_KEY"); - channelManagerKeyField.setAccessible(true); - var channelManagerKey = (String)channelManagerKeyField.get(null); - var kesselChannelManager = ChannelManager.getInstance(channelManagerKey); - - var insecureField = ChannelManager.class.getDeclaredField("insecureManagers"); - insecureField.setAccessible(true); - var insecureManagersSize = ((HashMap)insecureField.get(kesselChannelManager)).size(); - - assertEquals(0, insecureManagersSize); - - var manager = RelationsGrpcClientsManager.forInsecureClients("localhost:8080"); - insecureManagersSize = ((HashMap)insecureField.get(kesselChannelManager)).size(); - assertEquals(1, insecureManagersSize); - - RelationsGrpcClientsManager.shutdownManager(manager); - insecureManagersSize = ((HashMap)insecureField.get(kesselChannelManager)).size(); - assertEquals(0, insecureManagersSize); - - /* Shouldn't throw exception if executed twice */ - RelationsGrpcClientsManager.shutdownManager(manager); - insecureManagersSize = ((HashMap)insecureField.get(kesselChannelManager)).size(); - assertEquals(0, insecureManagersSize); - - var manager2 = RelationsGrpcClientsManager.forInsecureClients("localhost:8080"); - insecureManagersSize = ((HashMap)insecureField.get(kesselChannelManager)).size(); - assertEquals(1, insecureManagersSize); - assertNotEquals(manager, manager2); - - RelationsGrpcClientsManager.forInsecureClients("localhost:8081"); - insecureManagersSize = ((HashMap)insecureField.get(kesselChannelManager)).size(); - assertEquals(2, insecureManagersSize); - - RelationsGrpcClientsManager.shutdownAll(); - insecureManagersSize = ((HashMap)insecureField.get(kesselChannelManager)).size(); - assertEquals(0, insecureManagersSize); - } - public static Config.AuthenticationConfig dummyAuthConfigWithGoodOIDCClientCredentials() { return new Config.AuthenticationConfig() { @Override From a8e71760e9eb39ef32949eb7a9767530235f332f Mon Sep 17 00:00:00 2001 From: Mark McLaughlin Date: Thu, 22 Aug 2024 16:16:04 +0100 Subject: [PATCH 08/13] Add test. --- .../clients/ChannelManagerTest.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/test/java/org/project_kessel/clients/ChannelManagerTest.java b/src/test/java/org/project_kessel/clients/ChannelManagerTest.java index de15e00..556fc9f 100644 --- a/src/test/java/org/project_kessel/clients/ChannelManagerTest.java +++ b/src/test/java/org/project_kessel/clients/ChannelManagerTest.java @@ -15,11 +15,13 @@ class ChannelManagerTest { ChannelManager defaultChannelManager = ChannelManager.getInstance("defaultChannelManager"); + ChannelManager otherChannelManager = ChannelManager.getInstance("otherChannelManager"); @AfterEach void testTeardown() { /* Make sure all client managers shutdown/removed after each test */ defaultChannelManager.shutdownAll(); + otherChannelManager.shutdownAll(); } @Test @@ -59,6 +61,27 @@ void testManagerChannelReusePatterns() { assertNotEquals(four, five); } + @Test + void testManagerChannelReusePatternsAcrossChannelManagers() { + var one = defaultChannelManager.forInsecureClients("localhost:8080"); + var two = defaultChannelManager.forInsecureClients("localhost:8080"); // same as one + var three = otherChannelManager.forInsecureClients("localhost1:8080"); + var four = otherChannelManager.forSecureClients("localhost:8080"); + var five = otherChannelManager.forSecureClients("localhost1:8080"); + var six = defaultChannelManager.forSecureClients("localhost1:8080"); // same as five but different manager + + assertNotNull(one); + assertNotNull(two); + assertNotNull(three); + assertNotNull(four); + assertNotNull(five); + assertNotNull(six); + assertEquals(one, two); + assertNotEquals(two, three); + assertNotEquals(five, six); + assertNotEquals(four, five); + } + @Test void testThreadingChaos() { /* Basic testing to ensure that we don't get ConcurrentModificationExceptions, or any other exceptions, when From d62ddf8f8df53481c3af491c7ee504a56bc37fef Mon Sep 17 00:00:00 2001 From: Mark McLaughlin Date: Thu, 22 Aug 2024 16:42:14 +0100 Subject: [PATCH 09/13] Add newline to remove ugly circle! --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 1763f67..f433f05 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,4 @@ Thumbs.db target/ # Intellij -*.iml \ No newline at end of file +*.iml From 4e5d6455d0c0f2782bd61f6075b7cc2cb17e11df Mon Sep 17 00:00:00 2001 From: Mark McLaughlin Date: Thu, 22 Aug 2024 17:09:04 +0100 Subject: [PATCH 10/13] Fix channels naming. --- .../clients/ChannelManager.java | 40 ++++++------ .../clients/ChannelManagerTest.java | 62 +++++++++---------- 2 files changed, 51 insertions(+), 51 deletions(-) diff --git a/src/main/java/org/project_kessel/clients/ChannelManager.java b/src/main/java/org/project_kessel/clients/ChannelManager.java index bda42a0..f80d367 100644 --- a/src/main/java/org/project_kessel/clients/ChannelManager.java +++ b/src/main/java/org/project_kessel/clients/ChannelManager.java @@ -15,8 +15,8 @@ public final class ChannelManager { * hence channels, may be desirable. */ private static final Hashtable channelManagers = new Hashtable<>(); - private final HashMap insecureManagers = new HashMap<>(); - private final HashMap secureManagers = new HashMap<>(); + private final HashMap insecureChannels = new HashMap<>(); + private final HashMap secureChannels = new HashMap<>(); public static ChannelManager getInstance(String channelManagerKey) { if(!channelManagers.containsKey(channelManagerKey)) { @@ -27,62 +27,62 @@ public static ChannelManager getInstance(String channelManagerKey) { } public synchronized Channel forInsecureClients(String targetUrl) { - if (!insecureManagers.containsKey(targetUrl)) { + if (!insecureChannels.containsKey(targetUrl)) { var channel = Grpc.newChannelBuilder(targetUrl, InsecureChannelCredentials.create()).build(); - insecureManagers.put(targetUrl, channel); + insecureChannels.put(targetUrl, channel); } - return insecureManagers.get(targetUrl); + return insecureChannels.get(targetUrl); } public synchronized Channel forInsecureClients(String targetUrl, AuthenticationConfig authnConfig) throws RuntimeException { - if (!insecureManagers.containsKey(targetUrl)) { + if (!insecureChannels.containsKey(targetUrl)) { try { var channel = Grpc.newChannelBuilder(targetUrl, CompositeChannelCredentials.create(InsecureChannelCredentials.create(), CallCredentialsFactory.create(authnConfig))).build(); - insecureManagers.put(targetUrl, channel); + insecureChannels.put(targetUrl, channel); } catch (CallCredentialsFactory.CallCredentialsCreationException e) { throw new RuntimeException(e); } } - return insecureManagers.get(targetUrl); + return insecureChannels.get(targetUrl); } public synchronized Channel forSecureClients(String targetUrl) { - if (!secureManagers.containsKey(targetUrl)) { + if (!secureChannels.containsKey(targetUrl)) { var tlsChannelCredentials = TlsChannelCredentials.create(); var channel = Grpc.newChannelBuilder(targetUrl, tlsChannelCredentials).build(); - secureManagers.put(targetUrl, channel); + secureChannels.put(targetUrl, channel); } - return secureManagers.get(targetUrl); + return secureChannels.get(targetUrl); } public synchronized Channel forSecureClients(String targetUrl, AuthenticationConfig authnConfig) { - if (!secureManagers.containsKey(targetUrl)) { + if (!secureChannels.containsKey(targetUrl)) { var tlsChannelCredentials = TlsChannelCredentials.create(); try { var channel = Grpc.newChannelBuilder(targetUrl, CompositeChannelCredentials.create(tlsChannelCredentials, CallCredentialsFactory.create(authnConfig))).build(); - secureManagers.put(targetUrl, channel); + secureChannels.put(targetUrl, channel); } catch (CallCredentialsFactory.CallCredentialsCreationException e) { throw new RuntimeException(e); } } - return secureManagers.get(targetUrl); + return secureChannels.get(targetUrl); } public synchronized void shutdownAll() { - for (var channel : insecureManagers.values()) { + for (var channel : insecureChannels.values()) { channel.shutdown(); } - insecureManagers.clear(); - for (var channel : secureManagers.values()) { + insecureChannels.clear(); + for (var channel : secureChannels.values()) { channel.shutdown(); } - secureManagers.clear(); + secureChannels.clear(); } public synchronized void shutdownChannel(Channel channelToShutdown) { - var iter = insecureManagers.entrySet().iterator(); + var iter = insecureChannels.entrySet().iterator(); while (iter.hasNext()) { var entry = iter.next(); if(entry.getValue() == channelToShutdown) { @@ -91,7 +91,7 @@ public synchronized void shutdownChannel(Channel channelToShutdown) { return; } } - iter = secureManagers.entrySet().iterator(); + iter = secureChannels.entrySet().iterator(); while (iter.hasNext()) { var entry = iter.next(); if(entry.getValue() == channelToShutdown) { diff --git a/src/test/java/org/project_kessel/clients/ChannelManagerTest.java b/src/test/java/org/project_kessel/clients/ChannelManagerTest.java index 556fc9f..452fc16 100644 --- a/src/test/java/org/project_kessel/clients/ChannelManagerTest.java +++ b/src/test/java/org/project_kessel/clients/ChannelManagerTest.java @@ -100,13 +100,13 @@ void testThreadingChaos() { for (int i = 0; i < numberOfThreads / 3; i++) { final int j = i; service.submit(() -> { - Channel manager; + Channel channel; if(j % 2 == 0) { - manager = defaultChannelManager.forInsecureClients("localhost" + j); + channel = defaultChannelManager.forInsecureClients("localhost" + j); } else { - manager = defaultChannelManager.forSecureClients("localhost" + j); + channel = defaultChannelManager.forSecureClients("localhost" + j); } - channels.put("localhost" + j, manager); + channels.put("localhost" + j, channel); latch1.countDown(); }); @@ -128,13 +128,13 @@ void testThreadingChaos() { for (int i = numberOfThreads * 2 / 3; i < numberOfThreads; i++) { final int j = i - numberOfThreads * 2 / 3; service.submit(() -> { - Channel manager; + Channel channel; if(j % 2 == 0) { - manager = defaultChannelManager.forInsecureClients("localhost" + j); + channel = defaultChannelManager.forInsecureClients("localhost" + j); } else { - manager = defaultChannelManager.forSecureClients("localhost" + j); + channel = defaultChannelManager.forSecureClients("localhost" + j); } - channels.put("localhost" + j, manager); + channels.put("localhost" + j, channel); latch3.countDown(); }); @@ -159,49 +159,49 @@ void testManagerReuseInternal() throws Exception { defaultChannelManager.forSecureClients("localhost1:8080"); defaultChannelManager.forSecureClients("localhost1:8080"); // same as five - var insecureField = ChannelManager.class.getDeclaredField("insecureManagers"); + var insecureField = ChannelManager.class.getDeclaredField("insecureChannels"); insecureField.setAccessible(true); - var secureField = ChannelManager.class.getDeclaredField("secureManagers"); + var secureField = ChannelManager.class.getDeclaredField("secureChannels"); secureField.setAccessible(true); - var insecureManagers = (HashMap)insecureField.get(defaultChannelManager); - var secureManagers = (HashMap)secureField.get(defaultChannelManager); + var insecureChannels = (HashMap)insecureField.get(defaultChannelManager); + var secureChannels = (HashMap)secureField.get(defaultChannelManager); - assertEquals(2, insecureManagers.size()); - assertEquals(2, secureManagers.size()); + assertEquals(2, insecureChannels.size()); + assertEquals(2, secureChannels.size()); } @Test void testCreateAndShutdownPatternsInternal() throws Exception { - var insecureField = ChannelManager.class.getDeclaredField("insecureManagers"); + var insecureField = ChannelManager.class.getDeclaredField("insecureChannels"); insecureField.setAccessible(true); - var insecureManagersSize = ((HashMap)insecureField.get(defaultChannelManager)).size(); + var insecureChannelsSize = ((HashMap)insecureField.get(defaultChannelManager)).size(); - assertEquals(0, insecureManagersSize); + assertEquals(0, insecureChannelsSize); var channel = defaultChannelManager.forInsecureClients("localhost:8080"); - insecureManagersSize = ((HashMap)insecureField.get(defaultChannelManager)).size(); - assertEquals(1, insecureManagersSize); + insecureChannelsSize = ((HashMap)insecureField.get(defaultChannelManager)).size(); + assertEquals(1, insecureChannelsSize); defaultChannelManager.shutdownChannel(channel); - insecureManagersSize = ((HashMap)insecureField.get(defaultChannelManager)).size(); - assertEquals(0, insecureManagersSize); + insecureChannelsSize = ((HashMap)insecureField.get(defaultChannelManager)).size(); + assertEquals(0, insecureChannelsSize); /* Shouldn't throw exception if executed twice */ defaultChannelManager.shutdownChannel(channel); - insecureManagersSize = ((HashMap)insecureField.get(defaultChannelManager)).size(); - assertEquals(0, insecureManagersSize); + insecureChannelsSize = ((HashMap)insecureField.get(defaultChannelManager)).size(); + assertEquals(0, insecureChannelsSize); - var manager2 = defaultChannelManager.forInsecureClients("localhost:8080"); - insecureManagersSize = ((HashMap)insecureField.get(defaultChannelManager)).size(); - assertEquals(1, insecureManagersSize); - assertNotEquals(channel, manager2); + var channel2 = defaultChannelManager.forInsecureClients("localhost:8080"); + insecureChannelsSize = ((HashMap)insecureField.get(defaultChannelManager)).size(); + assertEquals(1, insecureChannelsSize); + assertNotEquals(channel, channel2); defaultChannelManager.forInsecureClients("localhost:8081"); - insecureManagersSize = ((HashMap)insecureField.get(defaultChannelManager)).size(); - assertEquals(2, insecureManagersSize); + insecureChannelsSize = ((HashMap)insecureField.get(defaultChannelManager)).size(); + assertEquals(2, insecureChannelsSize); defaultChannelManager.shutdownAll(); - insecureManagersSize = ((HashMap)insecureField.get(defaultChannelManager)).size(); - assertEquals(0, insecureManagersSize); + insecureChannelsSize = ((HashMap)insecureField.get(defaultChannelManager)).size(); + assertEquals(0, insecureChannelsSize); } } From 930294f0250e69d9c803271403963557f6303a82 Mon Sep 17 00:00:00 2001 From: Mark McLaughlin Date: Mon, 26 Aug 2024 11:01:32 +0100 Subject: [PATCH 11/13] Remove end to end test to common. --- .../RelationsGrpcClientsManagerTest.java | 57 ------------------- 1 file changed, 57 deletions(-) diff --git a/src/test/java/org/project_kessel/relations/client/RelationsGrpcClientsManagerTest.java b/src/test/java/org/project_kessel/relations/client/RelationsGrpcClientsManagerTest.java index 9eaca41..2712ceb 100644 --- a/src/test/java/org/project_kessel/relations/client/RelationsGrpcClientsManagerTest.java +++ b/src/test/java/org/project_kessel/relations/client/RelationsGrpcClientsManagerTest.java @@ -20,63 +20,6 @@ import static org.project_kessel.clients.util.CertUtil.*; public class RelationsGrpcClientsManagerTest { - private static final Metadata.Key authorizationKey = Metadata.Key.of("Authorization", Metadata.ASCII_STRING_MARSHALLER); - - @BeforeAll - static void testSetup() { - /* Make sure all client managers shutdown/removed before tests */ - RelationsGrpcClientsManager.shutdownAll(); - /* Add self-signed cert to keystore, trust manager and SSL context for TLS testing. */ - addTestCACertToTrustStore(); - } - - @AfterEach - void testTeardown() { - /* Make sure all client managers shutdown/removed after each test */ - RelationsGrpcClientsManager.shutdownAll(); - } - - @AfterAll - static void removeTestSetup() { - /* Remove self-signed cert */ - removeTestCACertFromKeystore(); - } - - /* - End-to-end tests against fake IdP and/or fake grpc relations-api - */ - - @Test - void testManagersHoldIntendedCredentialsInChannel() throws Exception { - Config.AuthenticationConfig authnConfig = dummyAuthConfigWithGoodOIDCClientCredentials(); - var manager = RelationsGrpcClientsManager.forInsecureClients("localhost:7000"); - var manager2 = RelationsGrpcClientsManager.forInsecureClients("localhost:7001", authnConfig); - var manager3 = RelationsGrpcClientsManager.forSecureClients("localhost:7002"); - var manager4 = RelationsGrpcClientsManager.forSecureClients("localhost:7003", authnConfig); - - var checkClient = manager.getCheckClient(); - var checkClient2 = manager2.getCheckClient(); - var checkClient3 = manager3.getCheckClient(); - var checkClient4 = manager4.getCheckClient(); - - var cd1 = GrpcServerSpy.runAgainstTemporaryServerWithDummyServices(7000, () -> checkClient.check(CheckRequest.getDefaultInstance())); - var cd2 = GrpcServerSpy.runAgainstTemporaryServerWithDummyServices(7001, () -> checkClient2.check(CheckRequest.getDefaultInstance())); - var cd3 = GrpcServerSpy.runAgainstTemporaryTlsServerWithDummyServices(7002, () -> checkClient3.check(CheckRequest.getDefaultInstance())); - var cd4 = GrpcServerSpy.runAgainstTemporaryTlsServerWithDummyServices(7003, () -> checkClient4.check(CheckRequest.getDefaultInstance())); - - assertNull(cd1.getMetadata().get(authorizationKey)); - assertEquals("NONE", cd1.getCall().getSecurityLevel().toString()); - - assertNotNull(cd2.getMetadata().get(authorizationKey)); - assertEquals("NONE", cd2.getCall().getSecurityLevel().toString()); - - assertNull(cd3.getMetadata().get(authorizationKey)); - assertEquals("PRIVACY_AND_INTEGRITY", cd3.getCall().getSecurityLevel().toString()); - - assertNotNull(cd4.getMetadata().get(authorizationKey)); - assertEquals("PRIVACY_AND_INTEGRITY", cd4.getCall().getSecurityLevel().toString()); - } - /* Tests relying on reflection. Brittle and could be removed in future. */ From 624819e4340bcdb7d648744cf375db57920e72b3 Mon Sep 17 00:00:00 2001 From: Mark McLaughlin Date: Mon, 26 Aug 2024 11:18:03 +0100 Subject: [PATCH 12/13] Replace common code with common dependency. --- pom.xml | 5 + .../clients/ChannelManager.java | 104 -------- .../project_kessel/clients/KesselClient.java | 16 -- .../clients/KesselClientsManager.java | 11 - .../clients/authn/AuthenticationConfig.java | 18 -- .../clients/authn/CallCredentialsFactory.java | 38 --- ...ClientCredentialsAuthenticationConfig.java | 65 ----- .../OIDCClientCredentialsCallCredentials.java | 100 -------- .../client/OIDCClientCredentialsMinter.java | 89 ------- .../NimbusOIDCClientCredentialsMinter.java | 69 ----- .../clients/ChannelManagerTest.java | 207 --------------- .../authn/CallCredentialsFactoryTest.java | 56 ----- ...CClientCredentialsCallCredentialsTest.java | 237 ------------------ .../OIDCClientCredentialsMinterTest.java | 96 ------- ...NimbusOIDCClientCredentialsMinterTest.java | 47 ---- .../project_kessel/clients/fake/FakeIdp.java | 105 -------- .../clients/fake/GrpcServerSpy.java | 172 ------------- .../project_kessel/clients/util/CertUtil.java | 70 ------ .../RelationsGrpcClientsManagerTest.java | 8 - 19 files changed, 5 insertions(+), 1508 deletions(-) delete mode 100644 src/main/java/org/project_kessel/clients/ChannelManager.java delete mode 100644 src/main/java/org/project_kessel/clients/KesselClient.java delete mode 100644 src/main/java/org/project_kessel/clients/KesselClientsManager.java delete mode 100644 src/main/java/org/project_kessel/clients/authn/AuthenticationConfig.java delete mode 100644 src/main/java/org/project_kessel/clients/authn/CallCredentialsFactory.java delete mode 100644 src/main/java/org/project_kessel/clients/authn/oidc/client/OIDCClientCredentialsAuthenticationConfig.java delete mode 100644 src/main/java/org/project_kessel/clients/authn/oidc/client/OIDCClientCredentialsCallCredentials.java delete mode 100644 src/main/java/org/project_kessel/clients/authn/oidc/client/OIDCClientCredentialsMinter.java delete mode 100644 src/main/java/org/project_kessel/clients/authn/oidc/client/nimbus/NimbusOIDCClientCredentialsMinter.java delete mode 100644 src/test/java/org/project_kessel/clients/ChannelManagerTest.java delete mode 100644 src/test/java/org/project_kessel/clients/authn/CallCredentialsFactoryTest.java delete mode 100644 src/test/java/org/project_kessel/clients/authn/oidc/client/OIDCClientCredentialsCallCredentialsTest.java delete mode 100644 src/test/java/org/project_kessel/clients/authn/oidc/client/OIDCClientCredentialsMinterTest.java delete mode 100644 src/test/java/org/project_kessel/clients/authn/oidc/client/nimbus/NimbusOIDCClientCredentialsMinterTest.java delete mode 100644 src/test/java/org/project_kessel/clients/fake/FakeIdp.java delete mode 100644 src/test/java/org/project_kessel/clients/fake/GrpcServerSpy.java delete mode 100644 src/test/java/org/project_kessel/clients/util/CertUtil.java diff --git a/pom.xml b/pom.xml index cf86100..fb843ba 100644 --- a/pom.xml +++ b/pom.xml @@ -39,6 +39,11 @@ + + org.project-kessel + common-client-java + 1.0-SNAPSHOT + jakarta.enterprise jakarta.enterprise.cdi-api diff --git a/src/main/java/org/project_kessel/clients/ChannelManager.java b/src/main/java/org/project_kessel/clients/ChannelManager.java deleted file mode 100644 index f80d367..0000000 --- a/src/main/java/org/project_kessel/clients/ChannelManager.java +++ /dev/null @@ -1,104 +0,0 @@ -package org.project_kessel.clients; - -import io.grpc.*; -import org.project_kessel.clients.authn.AuthenticationConfig; -import org.project_kessel.clients.authn.CallCredentialsFactory; - -import java.util.HashMap; -import java.util.Hashtable; - -public final class ChannelManager { - private static ChannelManager channelManager; - - /* In the scenario where multiple clients are using this library, we need to support multiple channel managers, - * so that clients can use separate channels. There may also be scenarios where reusing the same channel manager, and - * hence channels, may be desirable. */ - private static final Hashtable channelManagers = new Hashtable<>(); - - private final HashMap insecureChannels = new HashMap<>(); - private final HashMap secureChannels = new HashMap<>(); - - public static ChannelManager getInstance(String channelManagerKey) { - if(!channelManagers.containsKey(channelManagerKey)) { - channelManagers.put(channelManagerKey, new ChannelManager()); - } - - return channelManagers.get(channelManagerKey); - } - - public synchronized Channel forInsecureClients(String targetUrl) { - if (!insecureChannels.containsKey(targetUrl)) { - var channel = Grpc.newChannelBuilder(targetUrl, InsecureChannelCredentials.create()).build(); - insecureChannels.put(targetUrl, channel); - } - return insecureChannels.get(targetUrl); - } - - public synchronized Channel forInsecureClients(String targetUrl, AuthenticationConfig authnConfig) throws RuntimeException { - if (!insecureChannels.containsKey(targetUrl)) { - try { - var channel = Grpc.newChannelBuilder(targetUrl, - CompositeChannelCredentials.create(InsecureChannelCredentials.create(), CallCredentialsFactory.create(authnConfig))).build(); - insecureChannels.put(targetUrl, channel); - } catch (CallCredentialsFactory.CallCredentialsCreationException e) { - throw new RuntimeException(e); - } - } - return insecureChannels.get(targetUrl); - } - - public synchronized Channel forSecureClients(String targetUrl) { - if (!secureChannels.containsKey(targetUrl)) { - var tlsChannelCredentials = TlsChannelCredentials.create(); - var channel = Grpc.newChannelBuilder(targetUrl, tlsChannelCredentials).build(); - secureChannels.put(targetUrl, channel); - } - return secureChannels.get(targetUrl); - } - - public synchronized Channel forSecureClients(String targetUrl, AuthenticationConfig authnConfig) { - if (!secureChannels.containsKey(targetUrl)) { - var tlsChannelCredentials = TlsChannelCredentials.create(); - try { - var channel = Grpc.newChannelBuilder(targetUrl, - CompositeChannelCredentials.create(tlsChannelCredentials, CallCredentialsFactory.create(authnConfig))).build(); - secureChannels.put(targetUrl, channel); - } catch (CallCredentialsFactory.CallCredentialsCreationException e) { - throw new RuntimeException(e); - } - } - return secureChannels.get(targetUrl); - } - - public synchronized void shutdownAll() { - for (var channel : insecureChannels.values()) { - channel.shutdown(); - } - insecureChannels.clear(); - for (var channel : secureChannels.values()) { - channel.shutdown(); - } - secureChannels.clear(); - } - - public synchronized void shutdownChannel(Channel channelToShutdown) { - var iter = insecureChannels.entrySet().iterator(); - while (iter.hasNext()) { - var entry = iter.next(); - if(entry.getValue() == channelToShutdown) { - entry.getValue().shutdown(); - iter.remove(); - return; - } - } - iter = secureChannels.entrySet().iterator(); - while (iter.hasNext()) { - var entry = iter.next(); - if(entry.getValue() == channelToShutdown) { - entry.getValue().shutdown(); - iter.remove(); - return; - } - } - } -} diff --git a/src/main/java/org/project_kessel/clients/KesselClient.java b/src/main/java/org/project_kessel/clients/KesselClient.java deleted file mode 100644 index ebf6da7..0000000 --- a/src/main/java/org/project_kessel/clients/KesselClient.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.project_kessel.clients; - -import io.grpc.stub.AbstractAsyncStub; -import io.grpc.stub.AbstractBlockingStub; - -public abstract class KesselClient, B extends AbstractBlockingStub> { - protected A asyncStub; - protected B blockingStub; - - protected KesselClient(A asyncStub, B blockingStub) { - this.asyncStub = asyncStub; - this.blockingStub = blockingStub; - } - - -} diff --git a/src/main/java/org/project_kessel/clients/KesselClientsManager.java b/src/main/java/org/project_kessel/clients/KesselClientsManager.java deleted file mode 100644 index d79f4e4..0000000 --- a/src/main/java/org/project_kessel/clients/KesselClientsManager.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.project_kessel.clients; - -import io.grpc.Channel; - -public abstract class KesselClientsManager { - protected final Channel channel; - - protected KesselClientsManager(Channel channel) { - this.channel = channel; - } -} diff --git a/src/main/java/org/project_kessel/clients/authn/AuthenticationConfig.java b/src/main/java/org/project_kessel/clients/authn/AuthenticationConfig.java deleted file mode 100644 index 40aa4d6..0000000 --- a/src/main/java/org/project_kessel/clients/authn/AuthenticationConfig.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.project_kessel.clients.authn; - -public class AuthenticationConfig { - public enum AuthMode { - DISABLED, - OIDC_CLIENT_CREDENTIALS - } - - private AuthMode authMode; - - public AuthMode mode() { - return authMode; - } - - public void setMode(AuthMode authMode) { - this.authMode = authMode; - } -} diff --git a/src/main/java/org/project_kessel/clients/authn/CallCredentialsFactory.java b/src/main/java/org/project_kessel/clients/authn/CallCredentialsFactory.java deleted file mode 100644 index a3303a7..0000000 --- a/src/main/java/org/project_kessel/clients/authn/CallCredentialsFactory.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.project_kessel.clients.authn; - -import io.grpc.CallCredentials; -import org.project_kessel.clients.authn.oidc.client.OIDCClientCredentialsCallCredentials; - -public class CallCredentialsFactory { - - private CallCredentialsFactory() { - - } - - public static CallCredentials create(AuthenticationConfig authnConfig) throws CallCredentialsCreationException { - if (authnConfig == null) { - throw new CallCredentialsCreationException("AuthenticationConfig is required to create CallCredentials and must not be null."); - } - - try { - switch (authnConfig.mode()) { - case DISABLED: return null; - case OIDC_CLIENT_CREDENTIALS: return new OIDCClientCredentialsCallCredentials(authnConfig); - } - } catch (OIDCClientCredentialsCallCredentials.OIDCClientCredentialsCallCredentialsException e) { - throw new CallCredentialsCreationException("Failed to create OIDCClientCredentialsCallCredentials.", e); - } - - return null; - } - - public static class CallCredentialsCreationException extends Exception { - public CallCredentialsCreationException(String message) { - super(message); - } - - public CallCredentialsCreationException(String message, Throwable cause) { - super(message, cause); - } - } -} diff --git a/src/main/java/org/project_kessel/clients/authn/oidc/client/OIDCClientCredentialsAuthenticationConfig.java b/src/main/java/org/project_kessel/clients/authn/oidc/client/OIDCClientCredentialsAuthenticationConfig.java deleted file mode 100644 index 4ab1791..0000000 --- a/src/main/java/org/project_kessel/clients/authn/oidc/client/OIDCClientCredentialsAuthenticationConfig.java +++ /dev/null @@ -1,65 +0,0 @@ -package org.project_kessel.clients.authn.oidc.client; - -import org.project_kessel.clients.authn.AuthenticationConfig; - -import java.util.Optional; - -public class OIDCClientCredentialsAuthenticationConfig extends AuthenticationConfig { - OIDCClientCredentialsConfig credentialsConfig; - - public OIDCClientCredentialsConfig clientCredentialsConfig() { - return credentialsConfig; - } - - public void setCredentialsConfig(OIDCClientCredentialsConfig credentialsConfig) { - this.credentialsConfig = credentialsConfig; - } - - public static class OIDCClientCredentialsConfig { - String issuer; - String clientId; - String clientSecret; - Optional scope; - Optional oidcClientCredentialsMinterImplementation; - - public String issuer() { - return issuer; - } - - public void setIssuer(String issuer) { - this.issuer = issuer; - } - - public String clientId() { - return clientId; - } - - public void setClientId(String clientId) { - this.clientId = clientId; - } - - public String clientSecret() { - return clientSecret; - } - - public void setClientSecret(String clientSecret) { - this.clientSecret = clientSecret; - } - - public Optional scope() { - return scope; - } - - public void setScope(Optional scope) { - this.scope = scope; - } - - public Optional oidcClientCredentialsMinterImplementation() { - return oidcClientCredentialsMinterImplementation; - } - - public void setOidcClientCredentialsMinterImplementation(Optional oidcClientCredentialsMinterImplementation) { - this.oidcClientCredentialsMinterImplementation = oidcClientCredentialsMinterImplementation; - } - } -} diff --git a/src/main/java/org/project_kessel/clients/authn/oidc/client/OIDCClientCredentialsCallCredentials.java b/src/main/java/org/project_kessel/clients/authn/oidc/client/OIDCClientCredentialsCallCredentials.java deleted file mode 100644 index e0da0d1..0000000 --- a/src/main/java/org/project_kessel/clients/authn/oidc/client/OIDCClientCredentialsCallCredentials.java +++ /dev/null @@ -1,100 +0,0 @@ -package org.project_kessel.clients.authn.oidc.client; - -import io.grpc.Metadata; -import io.grpc.Status; -import org.project_kessel.clients.authn.AuthenticationConfig; - -import java.util.Optional; -import java.util.concurrent.Executor; -import java.util.concurrent.atomic.AtomicReference; - -public class OIDCClientCredentialsCallCredentials extends io.grpc.CallCredentials { - static final Metadata.Key authorizationKey = Metadata.Key.of("Authorization", Metadata.ASCII_STRING_MARSHALLER); - - private final OIDCClientCredentialsAuthenticationConfig.OIDCClientCredentialsConfig clientCredentialsConfig; - private final OIDCClientCredentialsMinter minter; - - private final AtomicReference storedBearerHeaderRef = new AtomicReference<>(); - - public OIDCClientCredentialsCallCredentials(AuthenticationConfig authnConfig) throws OIDCClientCredentialsCallCredentialsException { - this.clientCredentialsConfig = validateAndExtractConfig(authnConfig); - - Optional minterImpl = clientCredentialsConfig.oidcClientCredentialsMinterImplementation(); - try { - if(minterImpl.isPresent()) { - this.minter = OIDCClientCredentialsMinter.forName(minterImpl.get()); - } else { - this.minter = OIDCClientCredentialsMinter.forDefaultImplementation(); - } - } catch (OIDCClientCredentialsMinter.OIDCClientCredentialsMinterException e) { - throw new OIDCClientCredentialsCallCredentialsException("Couldn't create GrpcCallCredentials because minter impl not instantiated.", e); - } - } - - OIDCClientCredentialsCallCredentials(OIDCClientCredentialsAuthenticationConfig.OIDCClientCredentialsConfig clientCredentialsConfig, OIDCClientCredentialsMinter minter) { - this.clientCredentialsConfig = clientCredentialsConfig; - this.minter = minter; - } - - @Override - public void applyRequestMetadata(RequestInfo requestInfo, Executor appExecutor, MetadataApplier applier) { - appExecutor.execute(() -> { - try { - synchronized (storedBearerHeaderRef) { - if (storedBearerHeaderRef.get() == null || storedBearerHeaderRef.get().isExpired()) { - storedBearerHeaderRef.set(minter.authenticateAndRetrieveAuthorizationHeader(clientCredentialsConfig)); - } - - Metadata headers = new Metadata(); - headers.put(authorizationKey, storedBearerHeaderRef.get().getAuthorizationHeader()); - applier.apply(headers); - } - } catch (Exception e) { - applier.fail(Status.UNAUTHENTICATED.withCause(e)); - } - }); - } - - /** - * For unusual cases where stored credentials (i.e. token), which may be long-lived, is bad and needs to be flushed. - */ - public void flushStoredCredentials() { - synchronized (storedBearerHeaderRef) { - storedBearerHeaderRef.set(null); - } - } - - static OIDCClientCredentialsAuthenticationConfig.OIDCClientCredentialsConfig validateAndExtractConfig(AuthenticationConfig authnConfig) throws OIDCClientCredentialsCallCredentialsException { - if (!(authnConfig instanceof OIDCClientCredentialsAuthenticationConfig)) { - throw new OIDCClientCredentialsCallCredentialsException("ClientCredentialsConfig is required for OIDC client credentials authentication method."); - } - - var oidcClientCredentialsAuthenticationConfig = (OIDCClientCredentialsAuthenticationConfig) authnConfig; - if(oidcClientCredentialsAuthenticationConfig.clientCredentialsConfig() == null) { - throw new OIDCClientCredentialsCallCredentialsException("ClientCredentialsConfig cannot be null."); - } - - if(oidcClientCredentialsAuthenticationConfig.clientCredentialsConfig().issuer() == null) { - throw new OIDCClientCredentialsCallCredentialsException("ClientCredentialsConfig Issuer must not be null."); - } - if(oidcClientCredentialsAuthenticationConfig.clientCredentialsConfig().clientId() == null) { - throw new OIDCClientCredentialsCallCredentialsException("ClientCredentialsConfig Client id must not be null."); - } - if(oidcClientCredentialsAuthenticationConfig.clientCredentialsConfig().clientSecret() == null) { - throw new OIDCClientCredentialsCallCredentialsException("ClientCredentialsConfig Client secret must not be null."); - } - - return oidcClientCredentialsAuthenticationConfig.clientCredentialsConfig(); - } - - public static class OIDCClientCredentialsCallCredentialsException extends Exception { - public OIDCClientCredentialsCallCredentialsException(String message) { - super(message); - } - - public OIDCClientCredentialsCallCredentialsException(String message, Throwable cause) { - super(message, cause); - } - } - -} diff --git a/src/main/java/org/project_kessel/clients/authn/oidc/client/OIDCClientCredentialsMinter.java b/src/main/java/org/project_kessel/clients/authn/oidc/client/OIDCClientCredentialsMinter.java deleted file mode 100644 index e64f5cc..0000000 --- a/src/main/java/org/project_kessel/clients/authn/oidc/client/OIDCClientCredentialsMinter.java +++ /dev/null @@ -1,89 +0,0 @@ -package org.project_kessel.clients.authn.oidc.client; - -import org.project_kessel.clients.authn.oidc.client.nimbus.NimbusOIDCClientCredentialsMinter; - -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.time.LocalDateTime; -import java.util.Optional; - -public abstract class OIDCClientCredentialsMinter { - private static final Class defaultMinter = NimbusOIDCClientCredentialsMinter.class; - - public static OIDCClientCredentialsMinter forDefaultImplementation() throws OIDCClientCredentialsMinterException { - return forClass(defaultMinter); - } - - public static OIDCClientCredentialsMinter forClass(Class minterClass) throws OIDCClientCredentialsMinterException { - try { - Constructor constructor = minterClass.getConstructor(); - return (OIDCClientCredentialsMinter)constructor.newInstance(); - } catch (InvocationTargetException | NoSuchMethodException | InstantiationException | IllegalAccessException e) { - throw new OIDCClientCredentialsMinterException("Can't create instance of OIDC client credentials minter", e); - } - } - - public static OIDCClientCredentialsMinter forName(String name) throws OIDCClientCredentialsMinterException { - try { - Class minterImplClass = Class.forName(name); - return forClass(minterImplClass); - } catch(ClassNotFoundException e) { - throw new OIDCClientCredentialsMinterException("Can't find the specified OIDC client credentials minter implementation", e); - } - } - - public abstract BearerHeader authenticateAndRetrieveAuthorizationHeader(OIDCClientCredentialsAuthenticationConfig.OIDCClientCredentialsConfig clientConfig) throws OIDCClientCredentialsMinterException; - - public static class BearerHeader { - private final String authorizationHeader; - private final Optional expiry; - - public BearerHeader(String authorizationHeader, Optional expiry) { - this.authorizationHeader = authorizationHeader; - this.expiry = expiry; - } - - public String getAuthorizationHeader() { - return authorizationHeader; - } - - public boolean isExpired() { - return expiry.map(t -> t.isBefore(LocalDateTime.now())).orElse(true); - } - } - - /** - * Utility method to derive an expiry dateTime from a just granted token with expires_in set. - * @param expiresIn 0 is expected if expiresIn is not set or otherwise not applicable. - * @return - */ - public static Optional getExpiryDateFromExpiresIn(long expiresIn) { - Optional expiryTime; - if (expiresIn != 0) { - // this processing happens some time after token is granted with lifetime so subtract buffer from lifetime - long bufferSeconds = 60; - if(expiresIn < bufferSeconds) { - expiryTime = Optional.empty(); - } else { - expiryTime = Optional.of(LocalDateTime.now().plusSeconds(expiresIn).minusSeconds(bufferSeconds)); - } - } else { - expiryTime = Optional.empty(); - } - - return expiryTime; - } - - public static Class getDefaultMinterImplementation() { - return defaultMinter; - } - - public static class OIDCClientCredentialsMinterException extends Exception { - public OIDCClientCredentialsMinterException(String message) { - super(message); - } - public OIDCClientCredentialsMinterException(String message, Throwable cause) { - super(message, cause); - } - } -} diff --git a/src/main/java/org/project_kessel/clients/authn/oidc/client/nimbus/NimbusOIDCClientCredentialsMinter.java b/src/main/java/org/project_kessel/clients/authn/oidc/client/nimbus/NimbusOIDCClientCredentialsMinter.java deleted file mode 100644 index 74cad0c..0000000 --- a/src/main/java/org/project_kessel/clients/authn/oidc/client/nimbus/NimbusOIDCClientCredentialsMinter.java +++ /dev/null @@ -1,69 +0,0 @@ -package org.project_kessel.clients.authn.oidc.client.nimbus; - -import com.nimbusds.oauth2.sdk.*; -import com.nimbusds.oauth2.sdk.auth.ClientAuthentication; -import com.nimbusds.oauth2.sdk.auth.ClientSecretBasic; -import com.nimbusds.oauth2.sdk.auth.Secret; -import com.nimbusds.oauth2.sdk.id.ClientID; -import com.nimbusds.oauth2.sdk.id.Issuer; -import com.nimbusds.oauth2.sdk.token.BearerAccessToken; -import com.nimbusds.openid.connect.sdk.OIDCTokenResponse; -import com.nimbusds.openid.connect.sdk.OIDCTokenResponseParser; -import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata; -import io.quarkus.runtime.annotations.RegisterForReflection; -import org.project_kessel.clients.authn.oidc.client.OIDCClientCredentialsAuthenticationConfig; -import org.project_kessel.clients.authn.oidc.client.OIDCClientCredentialsMinter; - -import java.io.IOException; -import java.net.URI; -import java.time.LocalDateTime; -import java.util.Optional; - -/** - * Implementation pulled in by reflection in Vanilla java and registered for reflection if Quarkus native is used. - */ -@RegisterForReflection -public class NimbusOIDCClientCredentialsMinter extends OIDCClientCredentialsMinter { - @Override - public BearerHeader authenticateAndRetrieveAuthorizationHeader(OIDCClientCredentialsAuthenticationConfig.OIDCClientCredentialsConfig config) throws OIDCClientCredentialsMinterException { - Issuer issuer = new Issuer(config.issuer()); - ClientID clientID = new ClientID(config.clientId()); - Secret clientSecret = new Secret(config.clientSecret()); - Optional scope = config.scope().map(Scope::new); - AuthorizationGrant clientGrant = new ClientCredentialsGrant(); - - try { - OIDCProviderMetadata providerMetadata = OIDCProviderMetadata.resolve(issuer); - URI tokenEndpoint = providerMetadata.getTokenEndpointURI(); - ClientAuthentication clientAuth = new ClientSecretBasic(clientID, clientSecret); - // Make the token request - TokenRequest request; - if(scope.isPresent()) { - request = new TokenRequest(tokenEndpoint, clientAuth, clientGrant, scope.get()); - } else { - request = new TokenRequest(tokenEndpoint, clientAuth, clientGrant); - } - - TokenResponse tokenResponse = OIDCTokenResponseParser.parse(request.toHTTPRequest().send()); - if (!tokenResponse.indicatesSuccess()) { - TokenErrorResponse errorResponse = tokenResponse.toErrorResponse(); - String code = errorResponse.getErrorObject().getCode(); - String message = errorResponse.getErrorObject().getDescription(); - throw new OIDCClientCredentialsMinterException( - "Error requesting token from endpoint. TokenErrorResponse: code: " + code + ", message: " + message); - } - - OIDCTokenResponse successResponse = (OIDCTokenResponse)tokenResponse.toSuccessResponse(); - BearerAccessToken bearerAccessToken = successResponse.getOIDCTokens().getBearerAccessToken(); - - // Capture expiry if its exists in the token - long lifetime = bearerAccessToken.getLifetime(); - Optional expiryTime = getExpiryDateFromExpiresIn(lifetime); - - return new BearerHeader(bearerAccessToken.toAuthorizationHeader(), expiryTime); - } - catch(IOException | GeneralException e) { - throw new OIDCClientCredentialsMinterException("Failed to retrieve and parse OIDC well-known configuration from provider.", e); - } - } -} diff --git a/src/test/java/org/project_kessel/clients/ChannelManagerTest.java b/src/test/java/org/project_kessel/clients/ChannelManagerTest.java deleted file mode 100644 index 452fc16..0000000 --- a/src/test/java/org/project_kessel/clients/ChannelManagerTest.java +++ /dev/null @@ -1,207 +0,0 @@ -package org.project_kessel.clients; - -import io.grpc.Channel; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Test; - -import java.util.HashMap; -import java.util.Hashtable; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -import static io.smallrye.common.constraint.Assert.assertNotNull; -import static org.junit.jupiter.api.Assertions.*; - -class ChannelManagerTest { - ChannelManager defaultChannelManager = ChannelManager.getInstance("defaultChannelManager"); - ChannelManager otherChannelManager = ChannelManager.getInstance("otherChannelManager"); - - @AfterEach - void testTeardown() { - /* Make sure all client managers shutdown/removed after each test */ - defaultChannelManager.shutdownAll(); - otherChannelManager.shutdownAll(); - } - - @Test - void shouldSelectSameManager() { - ChannelManager channelManager = ChannelManager.getInstance("myChannelManangerKey"); - ChannelManager channelManager2 = ChannelManager.getInstance("myChannelManangerKey"); - - assertEquals(channelManager, channelManager2); - } - - @Test - void shouldNotSelectSameManager() { - ChannelManager channelManager = ChannelManager.getInstance("myChannelManangerKey"); - ChannelManager channelManager2 = ChannelManager.getInstance("anotherChannelManagerKey"); - - assertNotEquals(channelManager, channelManager2); - } - - @Test - void testManagerChannelReusePatterns() { - var one = defaultChannelManager.forInsecureClients("localhost:8080"); - var two = defaultChannelManager.forInsecureClients("localhost:8080"); // same as one - var three = defaultChannelManager.forInsecureClients("localhost1:8080"); - var four = defaultChannelManager.forSecureClients("localhost:8080"); - var five = defaultChannelManager.forSecureClients("localhost1:8080"); - var six = defaultChannelManager.forSecureClients("localhost1:8080"); // same as five - - assertNotNull(one); - assertNotNull(two); - assertNotNull(three); - assertNotNull(four); - assertNotNull(five); - assertNotNull(six); - assertEquals(one, two); - assertNotEquals(two, three); - assertEquals(five, six); - assertNotEquals(four, five); - } - - @Test - void testManagerChannelReusePatternsAcrossChannelManagers() { - var one = defaultChannelManager.forInsecureClients("localhost:8080"); - var two = defaultChannelManager.forInsecureClients("localhost:8080"); // same as one - var three = otherChannelManager.forInsecureClients("localhost1:8080"); - var four = otherChannelManager.forSecureClients("localhost:8080"); - var five = otherChannelManager.forSecureClients("localhost1:8080"); - var six = defaultChannelManager.forSecureClients("localhost1:8080"); // same as five but different manager - - assertNotNull(one); - assertNotNull(two); - assertNotNull(three); - assertNotNull(four); - assertNotNull(five); - assertNotNull(six); - assertEquals(one, two); - assertNotEquals(two, three); - assertNotEquals(five, six); - assertNotEquals(four, five); - } - - @Test - void testThreadingChaos() { - /* Basic testing to ensure that we don't get ConcurrentModificationExceptions, or any other exceptions, when - * creating and destroying managers on different threads. */ - - try { - Hashtable channels = new Hashtable<>(); - - int numberOfThreads = 100; - ExecutorService service = Executors.newFixedThreadPool(numberOfThreads); - CountDownLatch latch1 = new CountDownLatch(numberOfThreads / 3); - CountDownLatch latch2 = new CountDownLatch(numberOfThreads * 2 / 3 - numberOfThreads / 3); - CountDownLatch latch3 = new CountDownLatch(numberOfThreads - numberOfThreads * 2 / 3); - - /* A: Use 1/3 of threads to request/create channels at the same time. */ - for (int i = 0; i < numberOfThreads / 3; i++) { - final int j = i; - service.submit(() -> { - Channel channel; - if(j % 2 == 0) { - channel = defaultChannelManager.forInsecureClients("localhost" + j); - } else { - channel = defaultChannelManager.forSecureClients("localhost" + j); - } - channels.put("localhost" + j, channel); - - latch1.countDown(); - }); - } - - latch1.await(); - /* B and C, below, trigger at the same time once A is done. */ - - /* B: Use 1/3 of threads to shut down the above created channels. */ - for (int i = numberOfThreads / 3; i < numberOfThreads * 2 / 3; i++) { - final int j = i - numberOfThreads / 3; - service.submit(() -> { - defaultChannelManager.shutdownChannel(channels.get("localhost" + j)); - latch2.countDown(); - }); - } - - /* C: Use 1/3 of the threads to recreate/retrieve the same channels at the same time as B. */ - for (int i = numberOfThreads * 2 / 3; i < numberOfThreads; i++) { - final int j = i - numberOfThreads * 2 / 3; - service.submit(() -> { - Channel channel; - if(j % 2 == 0) { - channel = defaultChannelManager.forInsecureClients("localhost" + j); - } else { - channel = defaultChannelManager.forSecureClients("localhost" + j); - } - channels.put("localhost" + j, channel); - - latch3.countDown(); - }); - } - latch2.await(); - latch3.await(); - } catch(Exception e) { - fail("Should not have thrown any exception"); - } - } - - /* - Tests relying on reflection. Brittle and could be removed in future. - */ - - @Test - void testManagerReuseInternal() throws Exception { - defaultChannelManager.forInsecureClients("localhost:8080"); - defaultChannelManager.forInsecureClients("localhost:8080"); // same as one - defaultChannelManager.forInsecureClients("localhost1:8080"); - defaultChannelManager.forSecureClients("localhost:8080"); - defaultChannelManager.forSecureClients("localhost1:8080"); - defaultChannelManager.forSecureClients("localhost1:8080"); // same as five - - var insecureField = ChannelManager.class.getDeclaredField("insecureChannels"); - insecureField.setAccessible(true); - var secureField = ChannelManager.class.getDeclaredField("secureChannels"); - secureField.setAccessible(true); - var insecureChannels = (HashMap)insecureField.get(defaultChannelManager); - var secureChannels = (HashMap)secureField.get(defaultChannelManager); - - assertEquals(2, insecureChannels.size()); - assertEquals(2, secureChannels.size()); - } - - @Test - void testCreateAndShutdownPatternsInternal() throws Exception { - var insecureField = ChannelManager.class.getDeclaredField("insecureChannels"); - insecureField.setAccessible(true); - var insecureChannelsSize = ((HashMap)insecureField.get(defaultChannelManager)).size(); - - assertEquals(0, insecureChannelsSize); - - var channel = defaultChannelManager.forInsecureClients("localhost:8080"); - insecureChannelsSize = ((HashMap)insecureField.get(defaultChannelManager)).size(); - assertEquals(1, insecureChannelsSize); - - defaultChannelManager.shutdownChannel(channel); - insecureChannelsSize = ((HashMap)insecureField.get(defaultChannelManager)).size(); - assertEquals(0, insecureChannelsSize); - - /* Shouldn't throw exception if executed twice */ - defaultChannelManager.shutdownChannel(channel); - insecureChannelsSize = ((HashMap)insecureField.get(defaultChannelManager)).size(); - assertEquals(0, insecureChannelsSize); - - var channel2 = defaultChannelManager.forInsecureClients("localhost:8080"); - insecureChannelsSize = ((HashMap)insecureField.get(defaultChannelManager)).size(); - assertEquals(1, insecureChannelsSize); - assertNotEquals(channel, channel2); - - defaultChannelManager.forInsecureClients("localhost:8081"); - insecureChannelsSize = ((HashMap)insecureField.get(defaultChannelManager)).size(); - assertEquals(2, insecureChannelsSize); - - defaultChannelManager.shutdownAll(); - insecureChannelsSize = ((HashMap)insecureField.get(defaultChannelManager)).size(); - assertEquals(0, insecureChannelsSize); - } -} diff --git a/src/test/java/org/project_kessel/clients/authn/CallCredentialsFactoryTest.java b/src/test/java/org/project_kessel/clients/authn/CallCredentialsFactoryTest.java deleted file mode 100644 index 1034788..0000000 --- a/src/test/java/org/project_kessel/clients/authn/CallCredentialsFactoryTest.java +++ /dev/null @@ -1,56 +0,0 @@ -package org.project_kessel.clients.authn; - -import org.junit.jupiter.api.Test; -import org.project_kessel.clients.authn.oidc.client.OIDCClientCredentialsAuthenticationConfig; - -import java.util.Optional; - -import static org.junit.jupiter.api.Assertions.fail; - -public class CallCredentialsFactoryTest { - @Test - void testCreateOIDCClientCallCredentials() { - AuthenticationConfig authnConfig = dummyAuthConfigWithGoodOIDCClientCredentials(); - try { - CallCredentialsFactory.create(authnConfig); - } catch (CallCredentialsFactory.CallCredentialsCreationException e) { - fail("CallCredentialsFactory creation for OIDC client should not throw an exception when OIDC client config is good."); - } - } - - @Test - void testFailToCreateCallCredentialsWhenAuthnConfigEmpty() { - AuthenticationConfig authnConfig = null; - try { - CallCredentialsFactory.create(authnConfig); - fail("CallCredentialsFactory creation for OIDC client should throw an exception when OIDC client config is empty."); - } catch (CallCredentialsFactory.CallCredentialsCreationException e) { - } - } - - @Test - void testFailToCreateCallCredentialsForOIDCWhenConfigEmpty() { - var authnConfig = new OIDCClientCredentialsAuthenticationConfig(); - authnConfig.setMode(AuthenticationConfig.AuthMode.OIDC_CLIENT_CREDENTIALS); - try { - CallCredentialsFactory.create(authnConfig); - fail("CallCredentialsFactory creation for OIDC client should throw an exception when OIDC client config is empty."); - } catch (CallCredentialsFactory.CallCredentialsCreationException e) { - } - } - - public static OIDCClientCredentialsAuthenticationConfig dummyAuthConfigWithGoodOIDCClientCredentials() { - var oidcClientCredentialsConfig = new OIDCClientCredentialsAuthenticationConfig.OIDCClientCredentialsConfig(); - oidcClientCredentialsConfig.setIssuer("http://localhost:8090"); - oidcClientCredentialsConfig.setClientId("test"); - oidcClientCredentialsConfig.setClientSecret("test"); - oidcClientCredentialsConfig.setScope(Optional.empty()); - oidcClientCredentialsConfig.setOidcClientCredentialsMinterImplementation(Optional.empty()); - - var authnConfig = new OIDCClientCredentialsAuthenticationConfig(); - authnConfig.setMode(AuthenticationConfig.AuthMode.OIDC_CLIENT_CREDENTIALS); - authnConfig.setCredentialsConfig(oidcClientCredentialsConfig); - - return authnConfig; - } -} diff --git a/src/test/java/org/project_kessel/clients/authn/oidc/client/OIDCClientCredentialsCallCredentialsTest.java b/src/test/java/org/project_kessel/clients/authn/oidc/client/OIDCClientCredentialsCallCredentialsTest.java deleted file mode 100644 index cc8fdb2..0000000 --- a/src/test/java/org/project_kessel/clients/authn/oidc/client/OIDCClientCredentialsCallCredentialsTest.java +++ /dev/null @@ -1,237 +0,0 @@ -package org.project_kessel.clients.authn.oidc.client; - -import io.grpc.CallCredentials; -import io.grpc.Metadata; -import io.grpc.Status; -import io.grpc.netty.shaded.io.netty.util.concurrent.DefaultEventExecutor; -import org.junit.jupiter.api.Test; -import org.project_kessel.clients.authn.AuthenticationConfig; - -import java.time.LocalDateTime; -import java.util.Optional; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.atomic.AtomicReference; - -import static org.junit.jupiter.api.Assertions.*; -import static org.project_kessel.clients.authn.oidc.client.OIDCClientCredentialsMinter.getDefaultMinterImplementation; - -public class OIDCClientCredentialsCallCredentialsTest { - - @Test - void initializationShouldFailWithNullIssuer() { - try { - var authConfig = makeAuthConfig(null, "some", "some"); - new OIDCClientCredentialsCallCredentials(authConfig); - } - catch (OIDCClientCredentialsCallCredentials.OIDCClientCredentialsCallCredentialsException e) { - return; // expected - } - - fail("Issuer should not be null"); - } - - @Test - void initializationShouldFailWithNullClientId() { - try { - var authConfig = makeAuthConfig("some", null, "some"); - new OIDCClientCredentialsCallCredentials(authConfig); - } - catch (OIDCClientCredentialsCallCredentials.OIDCClientCredentialsCallCredentialsException e) { - return; // expected - } - - fail("Client id should not be null"); - } - - @Test - void initializationShouldFailWithNullClientSecret() { - try { - var authConfig = makeAuthConfig("some", "some", null); - new OIDCClientCredentialsCallCredentials(authConfig); - } - catch (OIDCClientCredentialsCallCredentials.OIDCClientCredentialsCallCredentialsException e) { - return; // expected - } - - fail("Client secret should not be null"); - } - - @Test - void unknownSpecifiedMinterShouldThrowException() { - var authConfig = makeAuthConfig("some", "some", "some", Optional.empty(), - Optional.of("one.bogus.clazz")); - try { - new OIDCClientCredentialsCallCredentials(authConfig); - } - catch (OIDCClientCredentialsCallCredentials.OIDCClientCredentialsCallCredentialsException e) { - return; // expected - } - - fail("Shouldn't be able to instantiate OIDCClientCredentialsCallCredentials with a bogus minter."); - } - - @Test - void knownSpecifiedMinterShouldNotThrowException() { - var authConfig = makeAuthConfig("some", "some", "some", Optional.empty(), - Optional.of(getDefaultMinterImplementation().getName())); - try { - new OIDCClientCredentialsCallCredentials(authConfig); - } - catch (OIDCClientCredentialsCallCredentials.OIDCClientCredentialsCallCredentialsException e) { - fail("Should be able create default minter with no problems."); - } - } - - @Test - void unspecifiedMinterShouldUseDefaultAndNotThrowException() { - var authConfig = makeAuthConfig("some", "some", "some", Optional.empty(), - Optional.empty()); - try { - new OIDCClientCredentialsCallCredentials(authConfig); - } - catch (OIDCClientCredentialsCallCredentials.OIDCClientCredentialsCallCredentialsException e) { - fail("Should be able create default minter with no problems."); - } - } - - @Test - void shouldApplyBearerMetadata() throws InterruptedException { - var authConfig = makeAuthConfig("some", "some", "some", Optional.empty(), - Optional.empty()); - var oidcClientCredentialsConfig = authConfig.clientCredentialsConfig(); - var minter = makeFakeMinter(true, 0); - var callCreds = new OIDCClientCredentialsCallCredentials(oidcClientCredentialsConfig, minter); - final AtomicReference metaDataRef = new AtomicReference<>(); - final AtomicReference statusRef = new AtomicReference<>(); - var latch = new CountDownLatch(1); - var metaDataApplier = makeFakeMetadataApplier(metaDataRef, statusRef, latch); - - callCreds.applyRequestMetadata(null, new DefaultEventExecutor(), metaDataApplier); - - latch.await(); - assertEquals("token0", metaDataRef.get().get(OIDCClientCredentialsCallCredentials.authorizationKey)); - assertNull(statusRef.get()); - } - - @Test - void shouldApplyPreviouslyObtainedTokenWhenInLifetime() throws InterruptedException { - var authConfig = makeAuthConfig("some", "some", "some", Optional.empty(), - Optional.empty()); - var oidcClientCredentialsConfig = authConfig.clientCredentialsConfig(); - var minter = makeFakeMinter(true, 100000); // big lifetime - var callCreds = new OIDCClientCredentialsCallCredentials(oidcClientCredentialsConfig, minter); - final AtomicReference metaDataRef = new AtomicReference<>(); - final AtomicReference statusRef = new AtomicReference<>(); - var latch = new CountDownLatch(1); - var metaDataApplier = makeFakeMetadataApplier(metaDataRef, statusRef, latch); - - callCreds.applyRequestMetadata(null, new DefaultEventExecutor(), metaDataApplier); - - latch.await(); - var latch2 = new CountDownLatch(1); - var metaDataApplier2 = makeFakeMetadataApplier(metaDataRef, statusRef, latch2); - - callCreds.applyRequestMetadata(null, new DefaultEventExecutor(), metaDataApplier2); - - latch2.await(); - // token0 is the original minted token -- shows there was no second authentication and new token - assertEquals("token0", metaDataRef.get().get(OIDCClientCredentialsCallCredentials.authorizationKey)); - assertNull(statusRef.get()); - } - - @Test - void shouldApplyNewTokenWhenOutOfLifetime() throws InterruptedException { - var authConfig = makeAuthConfig("some", "some", "some", Optional.empty(), - Optional.empty()); - var oidcClientCredentialsConfig = authConfig.clientCredentialsConfig(); - var minter = makeFakeMinter(true, 0); // zero lifetime forces new auth token - var callCreds = new OIDCClientCredentialsCallCredentials(oidcClientCredentialsConfig, minter); - final AtomicReference metaDataRef = new AtomicReference<>(); - final AtomicReference statusRef = new AtomicReference<>(); - var latch = new CountDownLatch(1); - var metaDataApplier = makeFakeMetadataApplier(metaDataRef, statusRef, latch); - - callCreds.applyRequestMetadata(null, new DefaultEventExecutor(), metaDataApplier); - - latch.await(); - var latch2 = new CountDownLatch(1); - var metaDataApplier2 = makeFakeMetadataApplier(metaDataRef, statusRef, latch2); - - callCreds.applyRequestMetadata(null, new DefaultEventExecutor(), metaDataApplier2); - - latch2.await(); - // token1 is the second minted token -- shows that when out of lifetime there is a second authn and new token - assertEquals("token1", metaDataRef.get().get(OIDCClientCredentialsCallCredentials.authorizationKey)); - assertNull(statusRef.get()); - } - - @Test - void shouldApplyUnauthenticatedWhenAuthnFails() throws InterruptedException { - var authConfig = makeAuthConfig("some", "some", "some", Optional.empty(), - Optional.empty()); - var oidcClientCredentialsConfig = authConfig.clientCredentialsConfig(); - var minter = makeFakeMinter(false, 0); - var callCreds = new OIDCClientCredentialsCallCredentials(oidcClientCredentialsConfig, minter); - final AtomicReference metaDataRef = new AtomicReference<>(); - final AtomicReference statusRef = new AtomicReference<>(); - var latch = new CountDownLatch(1); - var metaDataApplier = makeFakeMetadataApplier(metaDataRef, statusRef, latch); - - callCreds.applyRequestMetadata(null, new DefaultEventExecutor(), metaDataApplier); - - latch.await(); - assertNull(metaDataRef.get()); - assertEquals(Status.Code.UNAUTHENTICATED, statusRef.get().getCode()); - } - - static OIDCClientCredentialsMinter makeFakeMinter(boolean alwaysSucceedsOrFails, long tokensExpireIn) { - return new OIDCClientCredentialsMinter() { - int mintedNumber = 0; - - @Override - public BearerHeader authenticateAndRetrieveAuthorizationHeader(OIDCClientCredentialsAuthenticationConfig.OIDCClientCredentialsConfig clientConfig) throws OIDCClientCredentialsMinterException { - if (!alwaysSucceedsOrFails) { - throw new OIDCClientCredentialsMinterException("Authentication failed."); - } - - Optional expiry = Optional.of(LocalDateTime.now().plusSeconds(tokensExpireIn)); - return new BearerHeader("token" + mintedNumber++, expiry); - } - }; - } - - static CallCredentials.MetadataApplier makeFakeMetadataApplier(AtomicReference metaDataRef, AtomicReference statusRef, CountDownLatch latch) { - return new CallCredentials.MetadataApplier() { - @Override - public void apply(Metadata headers) { - metaDataRef.set(headers); - latch.countDown(); - } - - @Override - public void fail(Status status) { - statusRef.set(status); - latch.countDown(); - } - }; - } - - public static OIDCClientCredentialsAuthenticationConfig makeAuthConfig(String issuer, String clientId, String clientSecret) { - return makeAuthConfig(issuer, clientId, clientSecret, Optional.empty(), Optional.empty()); - } - - public static OIDCClientCredentialsAuthenticationConfig makeAuthConfig(String issuer, String clientId, String clientSecret, Optional scope, Optional minterImpl) { - var oidcClientCredentialsConfig = new OIDCClientCredentialsAuthenticationConfig.OIDCClientCredentialsConfig(); - oidcClientCredentialsConfig.setIssuer(issuer); - oidcClientCredentialsConfig.setClientId(clientId); - oidcClientCredentialsConfig.setClientSecret(clientSecret); - oidcClientCredentialsConfig.setScope(scope); - oidcClientCredentialsConfig.setOidcClientCredentialsMinterImplementation(minterImpl); - - var authnConfig = new OIDCClientCredentialsAuthenticationConfig(); - authnConfig.setMode(null); - authnConfig.setCredentialsConfig(oidcClientCredentialsConfig); - - return authnConfig; - } -} diff --git a/src/test/java/org/project_kessel/clients/authn/oidc/client/OIDCClientCredentialsMinterTest.java b/src/test/java/org/project_kessel/clients/authn/oidc/client/OIDCClientCredentialsMinterTest.java deleted file mode 100644 index 855bb98..0000000 --- a/src/test/java/org/project_kessel/clients/authn/oidc/client/OIDCClientCredentialsMinterTest.java +++ /dev/null @@ -1,96 +0,0 @@ -package org.project_kessel.clients.authn.oidc.client; - -import org.junit.jupiter.api.Test; - -import java.time.LocalDateTime; -import java.util.Optional; - -import static org.junit.jupiter.api.Assertions.*; -import static org.project_kessel.clients.authn.oidc.client.OIDCClientCredentialsMinter.getExpiryDateFromExpiresIn; - -class OIDCClientCredentialsMinterTest { - - @Test - void testCreateDefaultMinter() { - Class defaultMinterClass = OIDCClientCredentialsMinter.getDefaultMinterImplementation(); - try { - var minter = OIDCClientCredentialsMinter.forClass(defaultMinterClass); - assertInstanceOf(defaultMinterClass, minter); - } catch (OIDCClientCredentialsMinter.OIDCClientCredentialsMinterException e) { - fail("Creating minter from default implementation name should not throw an OIDCClientCredentialsMinterException"); - } - } - - @Test - void testCreateMinterFromClass() { - Class testMinterClass = TestMinter.class; - try { - var minter = OIDCClientCredentialsMinter.forClass(testMinterClass); - assertInstanceOf(testMinterClass, minter); - } catch (OIDCClientCredentialsMinter.OIDCClientCredentialsMinterException e) { - fail("Creating minter from test implementation name should not throw an OIDCClientCredentialsMinterException"); - } - } - - @Test - void testCreateMinterFromName() { - String testMinterName = TestMinter.class.getName(); - try { - OIDCClientCredentialsMinter.forName(testMinterName); - } catch (OIDCClientCredentialsMinter.OIDCClientCredentialsMinterException e) { - fail("Creating minter from test implementation name should not throw an OIDCClientCredentialsMinterException"); - } - } - - @Test - void testCreateMinterFromFakeImplNameThrowsException() { - String defaultMinterName = "absolutely.not.a.valid.Implementation"; - try { - OIDCClientCredentialsMinter.forName(defaultMinterName); - } catch (OIDCClientCredentialsMinter.OIDCClientCredentialsMinterException e) { - return; - } - fail("Creating minter from not existent implementation name should throw an OIDCClientCredentialsMinterException"); - } - - @Test - void testGetExpiryDateFromExpiresInLongLived() { - LocalDateTime someTimeBefore = LocalDateTime.now().plusSeconds(9000); - LocalDateTime someTimeAfter = LocalDateTime.now().plusSeconds(11000); - Optional expiryDate = getExpiryDateFromExpiresIn(10000); - assertTrue(expiryDate.isPresent()); - assertTrue(someTimeBefore.isBefore(expiryDate.get())); - assertTrue(someTimeAfter.isAfter(expiryDate.get())); - } - - @Test - void testGetAbsentExpiryDateFromExpiresInShortLived() { - Optional expiryDate = getExpiryDateFromExpiresIn(0); - assertTrue(expiryDate.isEmpty()); - } - - @Test - void bearerHeaderExpiryScenarios() { - Optional someTimeInTheFuture = Optional.of(LocalDateTime.now().plusSeconds(10000)); - var bearerHeader = new OIDCClientCredentialsMinter.BearerHeader("header", someTimeInTheFuture); - assertFalse(bearerHeader.isExpired()); - - Optional someTimeInThePast = Optional.of(LocalDateTime.now().minusSeconds(10000)); - bearerHeader = new OIDCClientCredentialsMinter.BearerHeader("header", someTimeInThePast); - assertTrue(bearerHeader.isExpired()); - - Optional noExpiryTime = Optional.empty(); - bearerHeader = new OIDCClientCredentialsMinter.BearerHeader("header", noExpiryTime); - assertTrue(bearerHeader.isExpired()); - } - - static class TestMinter extends OIDCClientCredentialsMinter { - public TestMinter() { - } - - @Override - public BearerHeader authenticateAndRetrieveAuthorizationHeader(OIDCClientCredentialsAuthenticationConfig.OIDCClientCredentialsConfig clientConfig) throws OIDCClientCredentialsMinterException { - return null; - } - } -} diff --git a/src/test/java/org/project_kessel/clients/authn/oidc/client/nimbus/NimbusOIDCClientCredentialsMinterTest.java b/src/test/java/org/project_kessel/clients/authn/oidc/client/nimbus/NimbusOIDCClientCredentialsMinterTest.java deleted file mode 100644 index 8ec2614..0000000 --- a/src/test/java/org/project_kessel/clients/authn/oidc/client/nimbus/NimbusOIDCClientCredentialsMinterTest.java +++ /dev/null @@ -1,47 +0,0 @@ -package org.project_kessel.clients.authn.oidc.client.nimbus; - -import org.junit.jupiter.api.Test; -import org.project_kessel.clients.authn.CallCredentialsFactoryTest; -import org.project_kessel.clients.authn.oidc.client.OIDCClientCredentialsMinter; -import org.project_kessel.clients.fake.FakeIdp; - -import static org.junit.jupiter.api.Assertions.*; - -public class NimbusOIDCClientCredentialsMinterTest { - - @Test - void shouldReturnBearerHeaderWhenIdPAuthenticates() { - var minter = new NimbusOIDCClientCredentialsMinter(); - var config = CallCredentialsFactoryTest.dummyAuthConfigWithGoodOIDCClientCredentials().clientCredentialsConfig(); - OIDCClientCredentialsMinter.BearerHeader bearerHeader = null; - try { - FakeIdp fakeIdp = new FakeIdp(8090); - fakeIdp.start(); - bearerHeader = minter.authenticateAndRetrieveAuthorizationHeader(config); - fakeIdp.stop(); - } catch (OIDCClientCredentialsMinter.OIDCClientCredentialsMinterException e) { - fail("Should not throw exception if authn is successful."); - } - - assertNotNull(bearerHeader); - assertEquals("Bearer blah", bearerHeader.getAuthorizationHeader()); - } - - @Test - void shouldThrowExceptionWhenIdPAuthenticationFails() { - var minter = new NimbusOIDCClientCredentialsMinter(); - var config = CallCredentialsFactoryTest.dummyAuthConfigWithGoodOIDCClientCredentials().clientCredentialsConfig(); - FakeIdp fakeIdp = new FakeIdp(8090, false); - try { - fakeIdp.start(); - minter.authenticateAndRetrieveAuthorizationHeader(config); - fail("Should throw exception if authn is not successful."); - } catch (OIDCClientCredentialsMinter.OIDCClientCredentialsMinterException e) { - // success - } catch(Exception e) { - fail("OIDCClientCredentialsMinterException expected."); - } finally { - fakeIdp.stop(); - } - } -} diff --git a/src/test/java/org/project_kessel/clients/fake/FakeIdp.java b/src/test/java/org/project_kessel/clients/fake/FakeIdp.java deleted file mode 100644 index 386254b..0000000 --- a/src/test/java/org/project_kessel/clients/fake/FakeIdp.java +++ /dev/null @@ -1,105 +0,0 @@ -package org.project_kessel.clients.fake; - -import com.sun.net.httpserver.HttpExchange; -import com.sun.net.httpserver.HttpHandler; -import com.sun.net.httpserver.HttpServer; - -import java.io.IOException; -import java.io.OutputStream; -import java.net.InetSocketAddress; - -/** - * Super-fake Idp that supports a hard-coded well-known discovery endpoint and a corresponding fake token endpoint. - * Does not use TLS. - */ -public class FakeIdp { - private final int port; - private final boolean alwaysSucceedOrFailAuthn; - HttpServer server = null; - - public FakeIdp(int port) { - this(port, true); - } - - public FakeIdp(int port, boolean alwaysSucceedOrFailAuthn) { - this.port = port; - this.alwaysSucceedOrFailAuthn = alwaysSucceedOrFailAuthn; - } - - public void start() { - try { - server = HttpServer.create(new InetSocketAddress(port), 0); - } catch (IOException e) { - throw new RuntimeException(e); - } - server.createContext("/.well-known/openid-configuration", new WellKnownHandler()); - if(alwaysSucceedOrFailAuthn) { - server.createContext("/token", new TokenHandler()); - } else { - server.createContext("/token", new UnauthorizedHandler()); - } - - server.setExecutor(null); // creates a default executor - server.start(); - } - - public void stop() { - server.stop(0); - } - - static class TokenHandler implements HttpHandler { - @Override - public void handle(HttpExchange t) throws IOException { - String response = "{\n" + - " \"iss\": \"http://localhost:8090/\",\n" + - " \"aud\": \"us\",\n" + - " \"sub\": \"usr_123\",\n" + - " \"scope\": \"read write\",\n" + - " \"iat\": 1458785796,\n" + - " \"exp\": 1458872196,\n" + - " \"token_type\": \"Bearer\",\n" + - " \"access_token\": \"blah\"\n" + - "}"; - t.getResponseHeaders().set("Content-Type", "application/json; charset=UTF-8"); - t.sendResponseHeaders(200, response.length()); - OutputStream os = t.getResponseBody(); - os.write(response.getBytes()); - os.close(); - } - } - - static class UnauthorizedHandler implements HttpHandler { - @Override - public void handle(HttpExchange t) throws IOException { - String response = "{\"error_description\":\"Access denied by resource owner or authorization server\",\"error\":\"access_denied\"}"; - - // https://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint (3.1.3.4. Token Error Response) - t.getResponseHeaders().set("Content-Type", "application/json; charset=UTF-8"); - t.sendResponseHeaders(400, response.length()); - OutputStream os = t.getResponseBody(); - os.write(response.getBytes()); - os.close(); - } - } - - static class WellKnownHandler implements HttpHandler { - @Override - public void handle(HttpExchange t) throws IOException { - String response = "{\n" + - "\t\"issuer\":\"http://localhost:8090\",\n" + - "\t\"authorization_endpoint\":\"http://localhost:8090/protocol/openid-connect/auth\",\n" + - "\t\"token_endpoint\":\"http://localhost:8090/token\",\n" + - "\t\"introspection_endpoint\":\"http://localhost:8090/token/introspect\",\n" + - "\t\"jwks_uri\":\"http://localhost:8090/certs\",\n" + - "\t\"response_types_supported\":[\"code\",\"none\",\"id_token\",\"token\",\"id_token token\",\"code id_token\",\"code token\",\"code id_token token\"],\n" + - "\t\"token_endpoint_auth_methods_supported\":[\"private_key_jwt\",\"client_secret_basic\",\"client_secret_post\",\"tls_client_auth\",\"client_secret_jwt\"],\n" + - "\t\"subject_types_supported\":[\"public\",\"pairwise\"]\n" + - "}"; - t.getResponseHeaders().set("Content-Type", "application/json; charset=UTF-8"); - t.sendResponseHeaders(200, response.length()); - OutputStream os = t.getResponseBody(); - os.write(response.getBytes()); - os.close(); - } - } -} diff --git a/src/test/java/org/project_kessel/clients/fake/GrpcServerSpy.java b/src/test/java/org/project_kessel/clients/fake/GrpcServerSpy.java deleted file mode 100644 index 35c1bfc..0000000 --- a/src/test/java/org/project_kessel/clients/fake/GrpcServerSpy.java +++ /dev/null @@ -1,172 +0,0 @@ -package org.project_kessel.clients.fake; - -import io.grpc.*; -import io.grpc.stub.StreamObserver; -import org.project_kessel.api.relations.v1beta1.*; - -import java.io.File; -import java.io.IOException; -import java.net.URL; -import java.util.Objects; -import java.util.concurrent.TimeUnit; - -public class GrpcServerSpy extends Server { - private final Server server; - - public GrpcServerSpy(int port, boolean tlsEnabled, ServerInterceptor interceptor, BindableService... services) { - ServerBuilder serverBuilder = ServerBuilder.forPort(port); - if (tlsEnabled) { - URL certsUrl = Thread.currentThread().getContextClassLoader().getResource("certs/test.crt"); - URL keyUrl = Thread.currentThread().getContextClassLoader().getResource("certs/test.key"); - File certFile = new File(Objects.requireNonNull(certsUrl).getPath()); - File keyFile = new File(Objects.requireNonNull(keyUrl).getPath()); - serverBuilder.useTransportSecurity(certFile, keyFile); - } - if (interceptor != null) { - serverBuilder.intercept(interceptor); - } - for (BindableService service : services) { - serverBuilder.addService(service); - } - server = serverBuilder.build(); - } - - public static ServerCallDetails runAgainstTemporaryServerWithDummyServices(int port, Call grpcCallFunction) { - return runAgainstTemporaryServerWithDummyServicesTlsSelect(port, false, grpcCallFunction); - } - - public static ServerCallDetails runAgainstTemporaryTlsServerWithDummyServices(int port, Call grpcCallFunction) { - return runAgainstTemporaryServerWithDummyServicesTlsSelect(port, true, grpcCallFunction); - } - - private static ServerCallDetails runAgainstTemporaryServerWithDummyServicesTlsSelect(int port, boolean tlsEnabled, Call grpcCallFunction) { - var dummyCheckService = new KesselCheckServiceGrpc.KesselCheckServiceImplBase() { - @Override - public void check(CheckRequest request, StreamObserver responseObserver) { - responseObserver.onNext(CheckResponse.getDefaultInstance()); - responseObserver.onCompleted(); - } - }; - var dummyTupleService = new KesselTupleServiceGrpc.KesselTupleServiceImplBase() { - @Override - public void readTuples(ReadTuplesRequest request, StreamObserver responseObserver) { - responseObserver.onNext(ReadTuplesResponse.getDefaultInstance()); - responseObserver.onCompleted(); - } - }; - var dummyLookupService = new KesselLookupServiceGrpc.KesselLookupServiceImplBase() { - @Override - public void lookupSubjects(LookupSubjectsRequest request, StreamObserver responseObserver) { - responseObserver.onNext(LookupSubjectsResponse.getDefaultInstance()); - responseObserver.onCompleted(); - } - }; - - return runAgainstTemporaryServerTlsSelect(port, tlsEnabled, grpcCallFunction, dummyCheckService, dummyTupleService, dummyLookupService); - } - - public static ServerCallDetails runAgainstTemporaryServer(int port, Call grpcCallFunction, BindableService... services) { - return runAgainstTemporaryServerTlsSelect(port, false, grpcCallFunction, services); - } - - public static ServerCallDetails runAgainstTemporaryTlsServer(int port, Call grpcCallFunction, BindableService... services) { - return runAgainstTemporaryServerTlsSelect(port, true, grpcCallFunction, services); - } - - private static ServerCallDetails runAgainstTemporaryServerTlsSelect(int port, boolean tlsEnabled, Call grpcCallFunction, BindableService... services) { - final ServerCallDetails serverCallDetails = new ServerCallDetails(); - - var spyInterceptor = new ServerInterceptor() { - @Override - public ServerCall.Listener interceptCall(ServerCall call, Metadata headers, ServerCallHandler next) { - serverCallDetails.setCall(call); - serverCallDetails.setMetadata(headers); - return next.startCall(call, headers); - } - }; - - FakeIdp fakeIdp = new FakeIdp(8090); - var serverSpy = new GrpcServerSpy(port, tlsEnabled, spyInterceptor, services); - - try { - fakeIdp.start(); - serverSpy.start(); - grpcCallFunction.call(); - serverSpy.shutdown(); - fakeIdp.stop(); - - return serverCallDetails; - } catch (Exception e) { - throw new RuntimeException(e); - } finally { - serverSpy.shutdown(); - fakeIdp.stop(); - } - } - - @Override - public Server start() throws IOException { - server.start(); - return this; - } - - @Override - public Server shutdown() { - server.shutdown(); - return this; - } - - @Override - public Server shutdownNow() { - server.shutdownNow(); - return this; - } - - @Override - public boolean isShutdown() { - return server.isShutdown(); - } - - @Override - public boolean isTerminated() { - return server.isTerminated(); - } - - @Override - public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { - return server.awaitTermination(timeout, unit); - } - - @Override - public void awaitTermination() throws InterruptedException { - server.awaitTermination(); - } - - public interface Call { - void call(); - } - - public static class ServerCallDetails { - private ServerCall call; - private Metadata metadata; - - public ServerCallDetails() { - } - - public ServerCall getCall() { - return call; - } - - public Metadata getMetadata() { - return metadata; - } - - public void setCall(ServerCall call) { - this.call = call; - } - - public void setMetadata(Metadata metadata) { - this.metadata = metadata; - } - } -} diff --git a/src/test/java/org/project_kessel/clients/util/CertUtil.java b/src/test/java/org/project_kessel/clients/util/CertUtil.java deleted file mode 100644 index 83e0bf5..0000000 --- a/src/test/java/org/project_kessel/clients/util/CertUtil.java +++ /dev/null @@ -1,70 +0,0 @@ -package org.project_kessel.clients.util; - -import java.io.*; -import java.security.*; -import java.security.cert.Certificate; -import java.security.cert.CertificateException; -import java.security.cert.CertificateFactory; - -public class CertUtil { - private static final char[] passphrase = "changeit".toCharArray(); - private static final String selfSignedAlias = "selfsigned"; - private static final String certFileName = "certs/test.crt"; - - public static void addTestCACertToTrustStore() { - try { - var keystore = loadKeystoreFromJdk(); - if (keystore.containsAlias(selfSignedAlias)) { - return; - } - - try(InputStream certIn = Thread.currentThread().getContextClassLoader().getResourceAsStream(certFileName); - BufferedInputStream bis = new BufferedInputStream(certIn)) { - - CertificateFactory cf = CertificateFactory.getInstance("X.509"); - while (bis.available() > 0) { - Certificate cert = cf.generateCertificate(bis); - keystore.setCertificateEntry(selfSignedAlias, cert); - } - - saveKeystoreToJdk(keystore); - } - } catch (CertificateException | KeyStoreException | IOException | NullPointerException e) { - throw new RuntimeException(e); - } - } - - public static void removeTestCACertFromKeystore() { - var keystore = loadKeystoreFromJdk(); - try { - keystore.deleteEntry(selfSignedAlias); - saveKeystoreToJdk(keystore); - } catch (KeyStoreException e) { - throw new RuntimeException(e); - } - } - - public static KeyStore loadKeystoreFromJdk() { - try (InputStream localCertIn = new FileInputStream(getCertFile())) { - KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType()); - keystore.load(localCertIn, passphrase); - return keystore; - } catch (IOException | CertificateException | KeyStoreException | NoSuchAlgorithmException e) { - throw new RuntimeException(e); - } - } - - public static void saveKeystoreToJdk(KeyStore keystore) { - try (OutputStream out = new FileOutputStream(getCertFile())) { - keystore.store(out, passphrase); - } catch (IOException | CertificateException | KeyStoreException | NoSuchAlgorithmException e) { - throw new RuntimeException(e); - } - } - - private static File getCertFile() { - final char sep = File.separatorChar; - File dir = new File(System.getProperty("java.home") + sep + "lib" + sep + "security"); - return new File(dir, "cacerts"); - } -} diff --git a/src/test/java/org/project_kessel/relations/client/RelationsGrpcClientsManagerTest.java b/src/test/java/org/project_kessel/relations/client/RelationsGrpcClientsManagerTest.java index 2712ceb..2e889d4 100644 --- a/src/test/java/org/project_kessel/relations/client/RelationsGrpcClientsManagerTest.java +++ b/src/test/java/org/project_kessel/relations/client/RelationsGrpcClientsManagerTest.java @@ -1,23 +1,15 @@ package org.project_kessel.relations.client; -import io.grpc.Metadata; -import org.junit.jupiter.api.AfterAll; -import org.project_kessel.api.relations.v1beta1.CheckRequest; import org.project_kessel.api.relations.v1beta1.KesselCheckServiceGrpc; import org.project_kessel.api.relations.v1beta1.KesselLookupServiceGrpc; import org.project_kessel.api.relations.v1beta1.KesselTupleServiceGrpc; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.project_kessel.clients.KesselClient; import org.project_kessel.clients.authn.AuthenticationConfig; -import org.project_kessel.clients.fake.GrpcServerSpy; import java.util.Optional; -import static io.smallrye.common.constraint.Assert.assertNotNull; import static org.junit.jupiter.api.Assertions.*; -import static org.project_kessel.clients.util.CertUtil.*; public class RelationsGrpcClientsManagerTest { /* From 1c13313eb65ae176bd7ceaeb4e32d0bb519ca794 Mon Sep 17 00:00:00 2001 From: Mark McLaughlin Date: Tue, 27 Aug 2024 14:06:53 +0100 Subject: [PATCH 13/13] Bump commons dependency version to published version. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index fb843ba..6dc5705 100644 --- a/pom.xml +++ b/pom.xml @@ -42,7 +42,7 @@ org.project-kessel common-client-java - 1.0-SNAPSHOT + 0.1 jakarta.enterprise