diff --git a/.gitignore b/.gitignore index 8cada35..f433f05 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,6 @@ Thumbs.db # Maven generated artifacts target/ + +# Intellij +*.iml diff --git a/pom.xml b/pom.xml index cf86100..6dc5705 100644 --- a/pom.xml +++ b/pom.xml @@ -39,6 +39,11 @@ + + org.project-kessel + common-client-java + 0.1 + jakarta.enterprise jakarta.enterprise.cdi-api 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/CDIManagedClients.java b/src/main/java/org/project_kessel/relations/client/CDIManagedRelationsClients.java similarity index 82% rename from src/main/java/org/project_kessel/relations/client/CDIManagedClients.java rename to src/main/java/org/project_kessel/relations/client/CDIManagedRelationsClients.java index dd9c5d1..22b582f 100644 --- a/src/main/java/org/project_kessel/relations/client/CDIManagedClients.java +++ b/src/main/java/org/project_kessel/relations/client/CDIManagedRelationsClients.java @@ -2,20 +2,21 @@ import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.inject.Produces; +import org.project_kessel.clients.authn.AuthenticationConfig.AuthMode; /** - * 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 -public class CDIManagedClients { +public class CDIManagedRelationsClients { @Produces 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/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/Config.java b/src/main/java/org/project_kessel/relations/client/Config.java index 96b3ecd..94bcd45 100644 --- a/src/main/java/org/project_kessel/relations/client/Config.java +++ b/src/main/java/org/project_kessel/relations/client/Config.java @@ -3,6 +3,7 @@ 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/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..a4229ff 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,38 @@ 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.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 final class RelationsGrpcClientsManager extends KesselClientsManager { + private RelationsGrpcClientsManager(Channel channel) { + super(channel); } - 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); - } + private static final String CHANNEL_MANAGER_KEY = RelationsGrpcClientsManager.class.getName(); - 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 static RelationsGrpcClientsManager forInsecureClients(String targetUrl) { + return new RelationsGrpcClientsManager(ChannelManager.getInstance(CHANNEL_MANAGER_KEY).forInsecureClients(targetUrl)); } - 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, Config.AuthenticationConfig authnConfig) throws RuntimeException { + return new RelationsGrpcClientsManager(ChannelManager.getInstance(CHANNEL_MANAGER_KEY).forInsecureClients(targetUrl, AuthnConfigConverter.convert(authnConfig))); } - 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 forSecureClients(String targetUrl) { + return new RelationsGrpcClientsManager(ChannelManager.getInstance(CHANNEL_MANAGER_KEY).forSecureClients(targetUrl)); } - 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, Config.AuthenticationConfig authnConfig) { + return new RelationsGrpcClientsManager(ChannelManager.getInstance(CHANNEL_MANAGER_KEY).forSecureClients(targetUrl, AuthnConfigConverter.convert(authnConfig))); } - /** - * 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 void shutdownAll() { + ChannelManager.getInstance(CHANNEL_MANAGER_KEY).shutdownAll(); } - /** - * 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(); - } - - private void closeClientChannel() { - channel.shutdown(); + public static void shutdownManager(RelationsGrpcClientsManager managerToShutdown) { + ChannelManager.getInstance(CHANNEL_MANAGER_KEY).shutdownChannel(managerToShutdown.channel); } public CheckClient getCheckClient() { @@ -128,5 +50,4 @@ public LookupClient getLookupClient() { public HealthClient getHealthClient() { return new HealthClient(channel); } - } diff --git a/src/main/java/org/project_kessel/relations/client/authn/CallCredentialsFactory.java b/src/main/java/org/project_kessel/relations/client/authn/CallCredentialsFactory.java deleted file mode 100644 index cd07d4e..0000000 --- a/src/main/java/org/project_kessel/relations/client/authn/CallCredentialsFactory.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.project_kessel.relations.client.authn; - -import io.grpc.CallCredentials; -import org.project_kessel.relations.client.Config; -import org.project_kessel.relations.client.authn.oidc.client.OIDCClientCredentialsCallCredentials; - -public class CallCredentialsFactory { - - private CallCredentialsFactory() { - - } - - public static CallCredentials create(Config.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/relations/client/authn/oidc/client/OIDCClientCredentialsCallCredentials.java b/src/main/java/org/project_kessel/relations/client/authn/oidc/client/OIDCClientCredentialsCallCredentials.java deleted file mode 100644 index 8c05648..0000000 --- a/src/main/java/org/project_kessel/relations/client/authn/oidc/client/OIDCClientCredentialsCallCredentials.java +++ /dev/null @@ -1,95 +0,0 @@ -package org.project_kessel.relations.client.authn.oidc.client; - -import io.grpc.Metadata; -import io.grpc.Status; -import org.project_kessel.relations.client.Config; - -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 Config.OIDCClientCredentialsConfig clientCredentialsConfig; - private final OIDCClientCredentialsMinter minter; - - private final AtomicReference storedBearerHeaderRef = new AtomicReference<>(); - - public OIDCClientCredentialsCallCredentials(Config.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(Config.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); - } - } - - /* 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()) { - throw new OIDCClientCredentialsCallCredentialsException("ClientCredentialsConfig is required for OIDC client credentials authentication method."); - } - if(authnConfig.clientCredentialsConfig().get().issuer() == null) { - throw new OIDCClientCredentialsCallCredentialsException("ClientCredentialsConfig Issuer must not be null."); - } - if(authnConfig.clientCredentialsConfig().get().clientId() == null) { - throw new OIDCClientCredentialsCallCredentialsException("ClientCredentialsConfig Client id must not be null."); - } - if(authnConfig.clientCredentialsConfig().get().clientSecret() == null) { - throw new OIDCClientCredentialsCallCredentialsException("ClientCredentialsConfig Client secret must not be null."); - } - - return authnConfig.clientCredentialsConfig().get(); - } - - 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/relations/client/authn/oidc/client/OIDCClientCredentialsMinter.java b/src/main/java/org/project_kessel/relations/client/authn/oidc/client/OIDCClientCredentialsMinter.java deleted file mode 100644 index 10dfc9a..0000000 --- a/src/main/java/org/project_kessel/relations/client/authn/oidc/client/OIDCClientCredentialsMinter.java +++ /dev/null @@ -1,89 +0,0 @@ -package org.project_kessel.relations.client.authn.oidc.client; - -import org.project_kessel.relations.client.Config; - -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 = org.project_kessel.relations.client.authn.oidc.client.nimbus.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(Config.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/relations/client/authn/oidc/client/nimbus/NimbusOIDCClientCredentialsMinter.java b/src/main/java/org/project_kessel/relations/client/authn/oidc/client/nimbus/NimbusOIDCClientCredentialsMinter.java deleted file mode 100644 index 378ea43..0000000 --- a/src/main/java/org/project_kessel/relations/client/authn/oidc/client/nimbus/NimbusOIDCClientCredentialsMinter.java +++ /dev/null @@ -1,69 +0,0 @@ -package org.project_kessel.relations.client.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.relations.client.Config; -import org.project_kessel.relations.client.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(Config.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/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/relations/client/CDIManagedRelationsClientsContainerTests.java similarity index 98% rename from src/test/java/org/project_kessel/relations/client/CDIManagedClientsContainerTests.java rename to src/test/java/org/project_kessel/relations/client/CDIManagedRelationsClientsContainerTests.java index fd5ca01..f9164ed 100644 --- a/src/test/java/org/project_kessel/relations/client/CDIManagedClientsContainerTests.java +++ b/src/test/java/org/project_kessel/relations/client/CDIManagedRelationsClientsContainerTests.java @@ -23,7 +23,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/relations/client/CDIManagedClientsTest.java b/src/test/java/org/project_kessel/relations/client/CDIManagedRelationsClientsTest.java similarity index 86% rename from src/test/java/org/project_kessel/relations/client/CDIManagedClientsTest.java rename to src/test/java/org/project_kessel/relations/client/CDIManagedRelationsClientsTest.java index 2258d60..0cdbe42 100644 --- a/src/test/java/org/project_kessel/relations/client/CDIManagedClientsTest.java +++ b/src/test/java/org/project_kessel/relations/client/CDIManagedRelationsClientsTest.java @@ -3,19 +3,20 @@ import org.junit.jupiter.api.Test; import org.mockito.MockedStatic; import org.mockito.Mockito; +import org.project_kessel.clients.authn.AuthenticationConfig; 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) @@ -38,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) @@ -64,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) @@ -90,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) @@ -135,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/relations/client/RelationsGrpcClientsManagerTest.java b/src/test/java/org/project_kessel/relations/client/RelationsGrpcClientsManagerTest.java index 1c72739..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,194 +1,21 @@ 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.relations.client.fake.GrpcServerSpy; +import org.project_kessel.clients.KesselClient; +import org.project_kessel.clients.authn.AuthenticationConfig; -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.*; -import static org.project_kessel.relations.client.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(); - } - - @Test - void testManagerReusePatterns() { - 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 + Tests relying on reflection. Brittle and could be removed in future. */ - @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. Maybe be 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 insecureField = RelationsGrpcClientsManager.class.getDeclaredField("insecureManagers"); - insecureField.setAccessible(true); - var secureField = RelationsGrpcClientsManager.class.getDeclaredField("secureManagers"); - secureField.setAccessible(true); - var insecureManagers = (HashMap)insecureField.get(null); - var secureManagers = (HashMap)secureField.get(null); - - assertEquals(2, insecureManagers.size()); - assertEquals(2, secureManagers.size()); - } - @Test void testSameChannelUsedByClientsInternal() throws Exception { var manager = RelationsGrpcClientsManager.forInsecureClients("localhost:8080"); @@ -196,13 +23,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(); @@ -210,46 +37,11 @@ void testSameChannelUsedByClientsInternal() throws Exception { assertEquals(lookupChannel, relationTuplesChannel); } - @Test - void testCreateAndShutdownPatternsInternal() throws Exception { - var insecureField = RelationsGrpcClientsManager.class.getDeclaredField("insecureManagers"); - insecureField.setAccessible(true); - var insecureManagersSize = ((HashMap)insecureField.get(null)).size(); - - assertEquals(0, insecureManagersSize); - - var manager = RelationsGrpcClientsManager.forInsecureClients("localhost:8080"); - insecureManagersSize = ((HashMap)insecureField.get(null)).size(); - assertEquals(1, insecureManagersSize); - - RelationsGrpcClientsManager.shutdownManager(manager); - insecureManagersSize = ((HashMap)insecureField.get(null)).size(); - assertEquals(0, insecureManagersSize); - - /* Shouldn't throw exception if executed twice */ - RelationsGrpcClientsManager.shutdownManager(manager); - insecureManagersSize = ((HashMap)insecureField.get(null)).size(); - assertEquals(0, insecureManagersSize); - - var manager2 = RelationsGrpcClientsManager.forInsecureClients("localhost:8080"); - insecureManagersSize = ((HashMap)insecureField.get(null)).size(); - assertEquals(1, insecureManagersSize); - assertNotEquals(manager, manager2); - - RelationsGrpcClientsManager.forInsecureClients("localhost:8081"); - insecureManagersSize = ((HashMap)insecureField.get(null)).size(); - assertEquals(2, insecureManagersSize); - - RelationsGrpcClientsManager.shutdownAll(); - insecureManagersSize = ((HashMap)insecureField.get(null)).size(); - assertEquals(0, insecureManagersSize); - } - 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 diff --git a/src/test/java/org/project_kessel/relations/client/authn/CallCredentialsFactoryTest.java b/src/test/java/org/project_kessel/relations/client/authn/CallCredentialsFactoryTest.java deleted file mode 100644 index 426f24a..0000000 --- a/src/test/java/org/project_kessel/relations/client/authn/CallCredentialsFactoryTest.java +++ /dev/null @@ -1,53 +0,0 @@ -package org.project_kessel.relations.client.authn; - -import org.junit.jupiter.api.Test; -import org.project_kessel.relations.client.Config; - -import java.util.Optional; - -import static org.junit.jupiter.api.Assertions.fail; -import static org.project_kessel.relations.client.RelationsGrpcClientsManagerTest.dummyAuthConfigWithGoodOIDCClientCredentials; - -class CallCredentialsFactoryTest { - - @Test - void testCreateOIDCClientCallCredentials() { - Config.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() { - Config.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() { - Config.AuthenticationConfig authnConfig = new Config.AuthenticationConfig() { - @Override - public Config.AuthMode mode() { - return Config.AuthMode.OIDC_CLIENT_CREDENTIALS; - } - - @Override - public Optional clientCredentialsConfig() { - return Optional.empty(); - } - }; - try { - CallCredentialsFactory.create(authnConfig); - fail("CallCredentialsFactory creation for OIDC client should throw an exception when OIDC client config is empty."); - } catch (CallCredentialsFactory.CallCredentialsCreationException e) { - } - } - -} diff --git a/src/test/java/org/project_kessel/relations/client/authn/oidc/client/OIDCClientCredentialsCallCredentialsTest.java b/src/test/java/org/project_kessel/relations/client/authn/oidc/client/OIDCClientCredentialsCallCredentialsTest.java deleted file mode 100644 index 0d92453..0000000 --- a/src/test/java/org/project_kessel/relations/client/authn/oidc/client/OIDCClientCredentialsCallCredentialsTest.java +++ /dev/null @@ -1,261 +0,0 @@ -package org.project_kessel.relations.client.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 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.relations.client.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().orElse(null); - 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().orElse(null); - 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().orElse(null); - 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().orElse(null); - 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(Config.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 Config.AuthenticationConfig 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; - } - - @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; - } - }); - } - }; - } -} diff --git a/src/test/java/org/project_kessel/relations/client/authn/oidc/client/OIDCClientCredentialsMinterTest.java b/src/test/java/org/project_kessel/relations/client/authn/oidc/client/OIDCClientCredentialsMinterTest.java deleted file mode 100644 index 8bc9d10..0000000 --- a/src/test/java/org/project_kessel/relations/client/authn/oidc/client/OIDCClientCredentialsMinterTest.java +++ /dev/null @@ -1,97 +0,0 @@ -package org.project_kessel.relations.client.authn.oidc.client; - -import org.junit.jupiter.api.Test; -import org.project_kessel.relations.client.Config; - -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; - -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(Config.OIDCClientCredentialsConfig clientConfig) throws OIDCClientCredentialsMinterException { - return null; - } - } -} diff --git a/src/test/java/org/project_kessel/relations/client/authn/oidc/client/nimbus/NimbusOIDCClientCredentialsMinterTest.java b/src/test/java/org/project_kessel/relations/client/authn/oidc/client/nimbus/NimbusOIDCClientCredentialsMinterTest.java deleted file mode 100644 index bc83e26..0000000 --- a/src/test/java/org/project_kessel/relations/client/authn/oidc/client/nimbus/NimbusOIDCClientCredentialsMinterTest.java +++ /dev/null @@ -1,47 +0,0 @@ -package org.project_kessel.relations.client.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.relations.client.fake.FakeIdp; - -import static org.junit.jupiter.api.Assertions.*; - -public class NimbusOIDCClientCredentialsMinterTest { - - @Test - void shouldReturnBearerHeaderWhenIdPAuthenticates() { - var minter = new NimbusOIDCClientCredentialsMinter(); - var config = RelationsGrpcClientsManagerTest.dummyAuthConfigWithGoodOIDCClientCredentials().clientCredentialsConfig(); - OIDCClientCredentialsMinter.BearerHeader bearerHeader = null; - try { - FakeIdp fakeIdp = new FakeIdp(8090); - fakeIdp.start(); - bearerHeader = minter.authenticateAndRetrieveAuthorizationHeader(config.get()); - 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 = RelationsGrpcClientsManagerTest.dummyAuthConfigWithGoodOIDCClientCredentials().clientCredentialsConfig(); - FakeIdp fakeIdp = new FakeIdp(8090, false); - try { - fakeIdp.start(); - minter.authenticateAndRetrieveAuthorizationHeader(config.get()); - 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/relations/client/fake/FakeIdp.java b/src/test/java/org/project_kessel/relations/client/fake/FakeIdp.java deleted file mode 100644 index b481455..0000000 --- a/src/test/java/org/project_kessel/relations/client/fake/FakeIdp.java +++ /dev/null @@ -1,105 +0,0 @@ -package org.project_kessel.relations.client.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/relations/client/fake/GrpcServerSpy.java b/src/test/java/org/project_kessel/relations/client/fake/GrpcServerSpy.java deleted file mode 100644 index e7b0e95..0000000 --- a/src/test/java/org/project_kessel/relations/client/fake/GrpcServerSpy.java +++ /dev/null @@ -1,172 +0,0 @@ -package org.project_kessel.relations.client.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/relations/client/util/CertUtil.java b/src/test/java/org/project_kessel/relations/client/util/CertUtil.java deleted file mode 100644 index e3c9bd4..0000000 --- a/src/test/java/org/project_kessel/relations/client/util/CertUtil.java +++ /dev/null @@ -1,70 +0,0 @@ -package org.project_kessel.relations.client.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"); - } -}