From de08f2e8c4fff0d08314476b5f7e1c52556a6770 Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Mon, 24 Mar 2025 15:38:25 -0400 Subject: [PATCH 01/52] maybe update dependencies --- pom.xml | 2 +- sdk/buf.gen.yaml | 4 ++-- sdk/pom.xml | 48 ++++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 45 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 7976a998..609d2376 100644 --- a/pom.xml +++ b/pom.xml @@ -138,7 +138,7 @@ org.jetbrains.kotlin kotlin-stdlib - 1.9.23 + 2.1.0-Beta2 diff --git a/sdk/buf.gen.yaml b/sdk/buf.gen.yaml index 6bb9c4e3..67d46e4c 100644 --- a/sdk/buf.gen.yaml +++ b/sdk/buf.gen.yaml @@ -10,5 +10,5 @@ managed: plugins: - plugin: buf.build/protocolbuffers/java:v25.3 out: ./ - - plugin: buf.build/grpc/java:v1.61.1 - out: ./ + - plugin: buf.build/connectrpc/kotlin + out: app/src/main/java diff --git a/sdk/pom.xml b/sdk/pom.xml index 494c74ec..6782e1dc 100644 --- a/sdk/pom.xml +++ b/sdk/pom.xml @@ -54,6 +54,30 @@ commons-codec 1.17.0 + + com.connectrpc + connect-kotlin + 0.7.2 + + + org.jetbrains.kotlin + kotlin-stdlib + + + com.squareup.okio + okio-jvm + + + org.jetbrains + annotations + + + + + com.squareup.okio + okio-jvm + 3.9.1 + io.github.erdtman java-json-canonicalization @@ -123,6 +147,12 @@ mockwebserver 5.0.0-alpha.14 test + + + com.squareup.okio + okio-jvm + + junit @@ -142,17 +172,23 @@ ${jazzer.version} test - - org.apache.commons - commons-compress - 1.26.1 - test - com.squareup.okhttp3 okhttp-tls 5.0.0-alpha.14 test + + + com.squareup.okio + okio-jvm + + + + + org.apache.commons + commons-compress + 1.26.1 + test org.mockito From 07873386978551734f43a57dce94896096cd3c21 Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Wed, 26 Mar 2025 13:32:39 -0400 Subject: [PATCH 02/52] here we go --- examples/pom.xml | 50 ++++++++++++++++++++++++------------------------ sdk/buf.gen.yaml | 2 +- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/examples/pom.xml b/examples/pom.xml index e782e24e..7c16f42d 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -130,31 +130,31 @@ 3.1.0 - - generateSources - generate-sources - - - - - - - - - - - - - - - run - - + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sdk/buf.gen.yaml b/sdk/buf.gen.yaml index 67d46e4c..f09a7bcf 100644 --- a/sdk/buf.gen.yaml +++ b/sdk/buf.gen.yaml @@ -11,4 +11,4 @@ plugins: - plugin: buf.build/protocolbuffers/java:v25.3 out: ./ - plugin: buf.build/connectrpc/kotlin - out: app/src/main/java + out: ./ From 96c52b17d2288bb1a1e1808cc894a2ddf8d1d134 Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Thu, 27 Mar 2025 19:04:05 -0400 Subject: [PATCH 03/52] get `KasClient` compiling --- pom.xml | 2 +- sdk/buf.gen.yaml | 28 ++-- sdk/buf.lock | 20 +-- sdk/buf.yaml | 21 +-- sdk/pom.xml | 136 +++++++++++++----- .../io/opentdf/platform/sdk/KASClient.java | 67 +++++---- 6 files changed, 185 insertions(+), 89 deletions(-) diff --git a/pom.xml b/pom.xml index 609d2376..a83fa937 100644 --- a/pom.xml +++ b/pom.xml @@ -138,7 +138,7 @@ org.jetbrains.kotlin kotlin-stdlib - 2.1.0-Beta2 + 2.1.20 diff --git a/sdk/buf.gen.yaml b/sdk/buf.gen.yaml index f09a7bcf..31b61143 100644 --- a/sdk/buf.gen.yaml +++ b/sdk/buf.gen.yaml @@ -1,14 +1,24 @@ -version: v1 +version: v2 managed: enabled: true - java_package_prefix: - default: io.opentdf.platform - except: - - buf.build/bufbuild/protovalidate - - buf.build/googleapis/googleapis - - buf.build/grpc-ecosystem/grpc-gateway + disable: + - file_option: java_package + module: buf.build/bufbuild/protovalidate + - file_option: java_package + module: buf.build/googleapis/googleapis + - file_option: java_package + module: buf.build/grpc-ecosystem/grpc-gateway + override: + - file_option: java_package_prefix + value: io.opentdf.platform plugins: - - plugin: buf.build/protocolbuffers/java:v25.3 + - remote: buf.build/protocolbuffers/java:v25.3 out: ./ - - plugin: buf.build/connectrpc/kotlin + - remote: buf.build/grpc/java:v1.61.1 out: ./ + - remote: buf.build/connectrpc/kotlin + out: ./ + opt: + - generateCallbackMethods=true + - generateCoroutineMethods=true + - generateBlockingUnaryMethods=true diff --git a/sdk/buf.lock b/sdk/buf.lock index deef61e8..6cc49ead 100644 --- a/sdk/buf.lock +++ b/sdk/buf.lock @@ -1,18 +1,12 @@ # Generated by buf. DO NOT EDIT. -version: v1 +version: v2 deps: - - remote: buf.build - owner: bufbuild - repository: protovalidate + - name: buf.build/bufbuild/protovalidate commit: f05a6f4403ce4327bae4f50f281c3ed0 - digest: shake256:668a0661b8df44d41839194896329330965fc215f3d2f88057fd60eeb759c2daf6cc6edfdd13b2a653d49fe2896ebedcb1a33c4c5b2dd10919f03ffb7fc52ae6 - - remote: buf.build - owner: googleapis - repository: googleapis + digest: b5:f1d76430ee97c89cd2044e9ae1c510887b701ee7bca60564ebf82e3919e53cacefc830a0eb803277c2d98c5f313b4167e8914afc9f214332717a50b5e170e6f4 + - name: buf.build/googleapis/googleapis commit: 7e6f6e774e29406da95bd61cdcdbc8bc - digest: shake256:fe43dd2265ea0c07d76bd925eeba612667cf4c948d2ce53d6e367e1b4b3cb5fa69a51e6acb1a6a50d32f894f054a35e6c0406f6808a483f2752e10c866ffbf73 - - remote: buf.build - owner: grpc-ecosystem - repository: grpc-gateway + digest: b5:654225f30f2351e6515417825836cb4bd5df3d952f6086e406957f80e03c8ee00c67739b836b87e06f2ff90a6f44675ad175e47ea5aef29ee909b91a29bdd334 + - name: buf.build/grpc-ecosystem/grpc-gateway commit: 3f42134f4c564983838425bc43c7a65f - digest: shake256:3d11d4c0fe5e05fda0131afefbce233940e27f0c31c5d4e385686aea58ccd30f72053f61af432fa83f1fc11cda57f5f18ca3da26a29064f73c5a0d076bba8d92 \ No newline at end of file + digest: b5:291b947d8ac09492517557e4e72e294788cb8201afc7d0df7bda80fa10931adb60d4d669208a7696bf24f1ecb2a33a16d4c1e766e6f31809248b00343119569b diff --git a/sdk/buf.yaml b/sdk/buf.yaml index 2dc8eb0e..0c330db3 100644 --- a/sdk/buf.yaml +++ b/sdk/buf.yaml @@ -1,22 +1,27 @@ -version: v1 +version: v2 deps: - buf.build/bufbuild/protovalidate - buf.build/googleapis/googleapis - buf.build/grpc-ecosystem/grpc-gateway -breaking: - use: - - FILE - - PACKAGE - - WIRE_JSON - - WIRE lint: - allow_comment_ignores: true use: - DEFAULT except: + - FIELD_NOT_REQUIRED + - PACKAGE_NO_IMPORT_CYCLE - PACKAGE_VERSION_SUFFIX ignore_only: PACKAGE_VERSION_SUFFIX: - google/api/annotations.proto - google/api/http.proto - google/protobuf/wrappers.proto +breaking: + use: + - FILE + - PACKAGE + - WIRE + - WIRE_JSON + except: + - EXTENSION_NO_DELETE + - FIELD_SAME_DEFAULT + - PACKAGE_EXTENSION_NO_DELETE diff --git a/sdk/pom.xml b/sdk/pom.xml index 6782e1dc..0d02ab32 100644 --- a/sdk/pom.xml +++ b/sdk/pom.xml @@ -12,6 +12,7 @@ 0.22.1 https://github.com/CodeIntelligenceTesting/jazzer/releases/download/v${jazzer.version} + 2.1.20 @@ -216,9 +217,50 @@ ${grpc.version} test + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + ${kotlin.version} + + + org.jetbrains.kotlin + kotlin-test + ${kotlin.version} + test + + + kotlin-maven-plugin + + + compile + compile + + compile + + + + src/main/java + target/generated-sources + + + + + test-compile + test-compile + + test-compile + + + + + 1.8 + + org.jetbrains.kotlin + ${kotlin.version} + org.apache.maven.plugins @@ -279,39 +321,39 @@ - - org.apache.maven.plugins - maven-antrun-plugin - 3.1.0 - - - - generateSources - generate-sources - - - - - - - - - - - - - - - - - - - - run - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + org.codehaus.mojo @@ -362,6 +404,34 @@ + + org.apache.maven.plugins + maven-compiler-plugin + + + default-compile + none + + + default-testCompile + none + + + compile + compile + + compile + + + + testCompile + test-compile + + testCompile + + + + diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java b/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java index def107fc..cb4c3a85 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java @@ -1,5 +1,7 @@ package io.opentdf.platform.sdk; +import com.connectrpc.ResponseMessage; +import com.connectrpc.ResponseMessageKt; import com.google.gson.Gson; import com.nimbusds.jose.JOSEException; import com.nimbusds.jose.JWSAlgorithm; @@ -11,6 +13,7 @@ import io.grpc.ManagedChannel; import io.grpc.StatusRuntimeException; import io.grpc.Status; +import io.opentdf.platform.kas.AccessServiceClient; import io.opentdf.platform.kas.AccessServiceGrpc; import io.opentdf.platform.kas.PublicKeyRequest; import io.opentdf.platform.kas.PublicKeyResponse; @@ -21,7 +24,7 @@ import io.opentdf.platform.sdk.nanotdf.NanoTDFType; import io.opentdf.platform.sdk.TDF.KasBadRequestException; -import java.nio.charset.StandardCharsets; +import kotlin.collections.MapsKt; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.net.MalformedURLException; @@ -33,6 +36,7 @@ import java.util.HashMap; import java.util.function.Function; +import static com.connectrpc.ResponseMessageKt.getOrThrow; import static io.opentdf.platform.sdk.TDF.GLOBAL_KEY_SALT; import static java.lang.String.format; @@ -43,7 +47,7 @@ */ public class KASClient implements SDK.KAS { - private final Function channelFactory; + private final Function channelFactory; private final RSASSASigner signer; private AsymDecryption decryptor; private String clientPublicKey; @@ -56,7 +60,7 @@ public class KASClient implements SDK.KAS { * communicate * @param dpopKey */ - public KASClient(Function channelFactory, RSAKey dpopKey) { + public KASClient(Function channelFactory, RSAKey dpopKey) { this.channelFactory = channelFactory; try { this.signer = new RSASSASigner(dpopKey); @@ -68,12 +72,17 @@ public KASClient(Function channelFactory, RSAKey dpopKey @Override public KASInfo getECPublicKey(Config.KASInfo kasInfo, NanoTDFType.ECCurve curve) { - var r = getStub(kasInfo.URL) - .publicKey( - PublicKeyRequest.newBuilder().setAlgorithm(String.format("ec:%s", curve.toString())).build()); + var req = PublicKeyRequest.newBuilder().setAlgorithm(String.format("ec:%s", curve.toString())).build(); + var r = getStub(kasInfo.URL).publicKeyBlocking(req, MapsKt.mapOf()).execute(); + PublicKeyResponse res; + try { + res = ResponseMessageKt.getOrThrow(r); + } catch (Exception e) { + throw new SDKException("error getting public key", e); + } var k2 = kasInfo.clone(); - k2.KID = r.getKid(); - k2.PublicKey = r.getPublicKey(); + k2.KID = res.getKid(); + k2.PublicKey = res.getPublicKey(); return k2; } @@ -88,7 +97,13 @@ public Config.KASInfo getPublicKey(Config.KASInfo kasInfo) { ? PublicKeyRequest.getDefaultInstance() : PublicKeyRequest.newBuilder().setAlgorithm(kasInfo.Algorithm).build(); - PublicKeyResponse resp = getStub(kasInfo.URL).publicKey(request); + var req= getStub(kasInfo.URL).publicKeyBlocking(request, MapsKt.mapOf()).execute(); + PublicKeyResponse resp; + try { + resp = getOrThrow(req); + } catch (Exception e) { + throw new SDKException("error getting public key", e); + } var kiCopy = new Config.KASInfo(); kiCopy.KID = resp.getKid(); @@ -134,11 +149,6 @@ private String normalizeAddress(String urlString) { @Override public synchronized void close() { - var entries = new ArrayList<>(stubs.values()); - stubs.clear(); - for (var entry : entries) { - entry.channel.shutdownNow(); - } } static class RewrapRequestBody { @@ -205,7 +215,12 @@ public byte[] unwrap(Manifest.KeyAccess keyAccess, String policy, KeyType sessi .build(); RewrapResponse response; try { - response = getStub(keyAccess.url).rewrap(request); + var req = getStub(keyAccess.url).rewrapBlocking(request, MapsKt.mapOf()).execute(); + try { + response = getOrThrow(req); + } catch (Exception e) { + throw new SDKException("error unwrapping key", e); + } var wrappedKey = response.getEntityWrappedKey().toByteArray(); if (sessionKeyType != KeyType.RSA2048Key) { @@ -264,12 +279,18 @@ public byte[] unwrapNanoTDF(NanoTDFType.ECCurve curve, String header, String kas throw new SDKException("error signing KAS request", e); } - var request = RewrapRequest + var req = RewrapRequest .newBuilder() .setSignedRequestToken(jwt.serialize()) .build(); - var response = getStub(keyAccess.url).rewrap(request); + var request = getStub(keyAccess.url).rewrapBlocking(req, MapsKt.mapOf()).execute(); + RewrapResponse response; + try { + response = getOrThrow(request); + } catch (Exception e) { + throw new SDKException("error rewrapping key", e); + } var wrappedKey = response.getEntityWrappedKey().toByteArray(); // Generate symmetric key @@ -291,7 +312,7 @@ public byte[] unwrapNanoTDF(NanoTDFType.ECCurve curve, String header, String kas return gcm.decrypt(encrypted); } - private final HashMap stubs = new HashMap<>(); + private final HashMap stubs = new HashMap<>(); private static class CacheEntry { final ManagedChannel channel; @@ -304,14 +325,10 @@ private CacheEntry(ManagedChannel channel, AccessServiceGrpc.AccessServiceBlocki } // make this protected so we can test the address normalization logic - synchronized AccessServiceGrpc.AccessServiceBlockingStub getStub(String url) { + synchronized AccessServiceClient getStub(String url) { var realAddress = normalizeAddress(url); - if (!stubs.containsKey(realAddress)) { - var channel = channelFactory.apply(realAddress); - var stub = AccessServiceGrpc.newBlockingStub(channel); - stubs.put(realAddress, new CacheEntry(channel, stub)); - } + stubs.computeIfAbsent(realAddress, channelFactory); - return stubs.get(realAddress).stub; + return stubs.get(realAddress); } } From 32a5094a5ce4f3e31503e4ae9ce67124e62774d8 Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Wed, 2 Apr 2025 13:37:15 -0400 Subject: [PATCH 04/52] just saving --- sdk/pom.xml | 5 ++++ .../io/opentdf/platform/sdk/KASClient.java | 2 -- .../io/opentdf/platform/sdk/SDKBuilder.java | 14 +++++++++++ .../java/io/opentdf/platform/sdk/TDF.java | 1 - .../opentdf/platform/sdk/connectrpc/Cont.kt | 25 +++++++++++++++++++ 5 files changed, 44 insertions(+), 3 deletions(-) create mode 100644 sdk/src/main/java/io/opentdf/platform/sdk/connectrpc/Cont.kt diff --git a/sdk/pom.xml b/sdk/pom.xml index 0d02ab32..8308a2ed 100644 --- a/sdk/pom.xml +++ b/sdk/pom.xml @@ -74,6 +74,11 @@ + + com.connectrpc + connect-kotlin-okhttp + 0.7.2 + com.squareup.okio okio-jvm diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java b/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java index cb4c3a85..4566c917 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java @@ -1,6 +1,5 @@ package io.opentdf.platform.sdk; -import com.connectrpc.ResponseMessage; import com.connectrpc.ResponseMessageKt; import com.google.gson.Gson; import com.nimbusds.jose.JOSEException; @@ -31,7 +30,6 @@ import java.net.URL; import java.time.Duration; import java.time.Instant; -import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.function.Function; diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java b/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java index 9f4e2cd9..fe2fc638 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java @@ -1,5 +1,8 @@ package io.opentdf.platform.sdk; +import com.connectrpc.ProtocolClientInterface; +import com.connectrpc.impl.ProtocolClient; +import com.connectrpc.okhttp.ConnectOkHttpClient; import com.nimbusds.jose.JOSEException; import com.nimbusds.jose.jwk.KeyUse; import com.nimbusds.jose.jwk.RSAKey; @@ -17,11 +20,13 @@ import com.nimbusds.oauth2.sdk.tokenexchange.TokenExchangeGrant; import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata; import io.grpc.*; +import io.opentdf.platform.kas.AccessServiceClient; import io.opentdf.platform.wellknownconfiguration.GetWellKnownConfigurationRequest; import io.opentdf.platform.wellknownconfiguration.GetWellKnownConfigurationResponse; import io.opentdf.platform.wellknownconfiguration.WellKnownServiceGrpc; import nl.altindag.ssl.SSLFactory; import nl.altindag.ssl.pem.util.PemUtils; +import okhttp3.OkHttpClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -234,6 +239,15 @@ ServicesAndInternals buildServices() { managedChannelFactory = (String endpoint) -> getManagedChannelBuilder(endpoint).intercept(authInterceptor) .build(); } + var c = new OkHttpClient.Builder() + .protocols(OkHttpClient.Companion.getDEFAULT_PROTOCOLS$okhttp()) + .connectionSpecs(OkHttpClient.Companion.getDEFAULT_CONNECTION_SPECS$okhttp()) + .sslSocketFactory() + .build(); + + var as = new ProtocolClient( + new ConnectOkHttpClient(), + ) var client = new KASClient(managedChannelFactory, dpopKey); return new ServicesAndInternals( authInterceptor, diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java b/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java index 5862f00e..dc8bd74b 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java @@ -13,7 +13,6 @@ import io.opentdf.platform.sdk.nanotdf.ECKeyPair; import org.apache.commons.codec.DecoderException; import org.apache.commons.codec.binary.Hex; -import org.bouncycastle.crypto.digests.SHA256Digest; import org.bouncycastle.jce.interfaces.ECPublicKey; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/connectrpc/Cont.kt b/sdk/src/main/java/io/opentdf/platform/sdk/connectrpc/Cont.kt new file mode 100644 index 00000000..4e9bfce3 --- /dev/null +++ b/sdk/src/main/java/io/opentdf/platform/sdk/connectrpc/Cont.kt @@ -0,0 +1,25 @@ +package io.opentdf.platform.sdk.connectrpc + +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers +import java.util.function.BiConsumer +import kotlin.coroutines.Continuation +import kotlin.coroutines.CoroutineContext +interface Cont { + companion object { + @JvmOverloads + fun getContinuation( + onFinished: BiConsumer, + dispatcher: CoroutineDispatcher = Dispatchers.Default + ): Continuation { + return object : Continuation { + override val context: CoroutineContext + get() = dispatcher + + override fun resumeWith(result: Result) { + onFinished.accept(result.getOrNull(), result.exceptionOrNull()) + } + } + } + } +} From 218dca567c319f733ece0a9169af3d261e719d83 Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Wed, 9 Apr 2025 09:09:22 -0400 Subject: [PATCH 05/52] just saving --- .../main/java/io/opentdf/platform/sdk/SDKBuilder.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java b/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java index fe2fc638..7002ff83 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java @@ -1,6 +1,8 @@ package io.opentdf.platform.sdk; +import com.connectrpc.ProtocolClientConfig; import com.connectrpc.ProtocolClientInterface; +import com.connectrpc.SerializationStrategy; import com.connectrpc.impl.ProtocolClient; import com.connectrpc.okhttp.ConnectOkHttpClient; import com.nimbusds.jose.JOSEException; @@ -242,12 +244,13 @@ ServicesAndInternals buildServices() { var c = new OkHttpClient.Builder() .protocols(OkHttpClient.Companion.getDEFAULT_PROTOCOLS$okhttp()) .connectionSpecs(OkHttpClient.Companion.getDEFAULT_CONNECTION_SPECS$okhttp()) - .sslSocketFactory() + .socketFactory(sslFactory.getSslSocketFactory()) .build(); var as = new ProtocolClient( - new ConnectOkHttpClient(), - ) + new ConnectOkHttpClient(c), + new ProtocolClientConfig(platformEndpoint, ) + ); var client = new KASClient(managedChannelFactory, dpopKey); return new ServicesAndInternals( authInterceptor, From ab13ca9ebb7a89c0b7fb8cb27401dc33b65cc335 Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Thu, 17 Apr 2025 16:45:58 -0400 Subject: [PATCH 06/52] trying to get things working --- sdk/pom.xml | 25 +- .../opentdf/platform/sdk/Autoconfigure.java | 11 +- .../io/opentdf/platform/sdk/KASClient.java | 66 +- .../java/io/opentdf/platform/sdk/SDK.java | 58 +- .../io/opentdf/platform/sdk/SDKBuilder.java | 141 +- .../java/io/opentdf/platform/sdk/TDF.java | 6 +- ...CAuthInterceptor.java => TokenSource.java} | 86 +- .../io/opentdf/platform/AuthInterceptor.kt | 58 + .../kotlin/io/opentdf/platform/Helpers.kt | 36 + .../platform/sdk/AutoconfigureTest.java | 1879 +++++++++-------- .../opentdf/platform/sdk/KASClientTest.java | 57 +- .../opentdf/platform/sdk/SDKBuilderTest.java | 41 +- 12 files changed, 1296 insertions(+), 1168 deletions(-) rename sdk/src/main/java/io/opentdf/platform/sdk/{GRPCAuthInterceptor.java => TokenSource.java} (67%) create mode 100644 sdk/src/main/kotlin/io/opentdf/platform/AuthInterceptor.kt create mode 100644 sdk/src/main/kotlin/io/opentdf/platform/Helpers.kt diff --git a/sdk/pom.xml b/sdk/pom.xml index cd29f840..78876a59 100644 --- a/sdk/pom.xml +++ b/sdk/pom.xml @@ -74,11 +74,6 @@ - - com.connectrpc - connect-kotlin-okhttp - 0.7.2 - com.squareup.okio okio-jvm @@ -108,6 +103,22 @@ io.grpc grpc-stub + + com.squareup.okhttp3 + okhttp + 4.12.0 + + + com.connectrpc + connect-kotlin-google-java-ext + 0.7.2 + + + com.connectrpc + connect-kotlin-okhttp + 0.7.2 + + org.apache.tomcat @@ -151,7 +162,7 @@ com.squareup.okhttp3 mockwebserver - 5.0.0-alpha.14 + 4.12.0 test @@ -181,7 +192,7 @@ com.squareup.okhttp3 okhttp-tls - 5.0.0-alpha.14 + 4.12.0 test diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/Autoconfigure.java b/sdk/src/main/java/io/opentdf/platform/sdk/Autoconfigure.java index 6113a681..5398b0e4 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/Autoconfigure.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/Autoconfigure.java @@ -1,6 +1,5 @@ package io.opentdf.platform.sdk; -import com.google.common.base.Supplier; import io.opentdf.platform.policy.Attribute; import io.opentdf.platform.policy.AttributeRuleTypeEnum; import io.opentdf.platform.policy.AttributeValueSelector; @@ -8,7 +7,7 @@ import io.opentdf.platform.policy.KasPublicKeyAlgEnum; import io.opentdf.platform.policy.KeyAccessServer; import io.opentdf.platform.policy.Value; -import io.opentdf.platform.policy.attributes.AttributesServiceGrpc.AttributesServiceFutureStub; +import io.opentdf.platform.policy.attributes.AttributesServiceClient; import io.opentdf.platform.policy.attributes.GetAttributeValuesByFqnsRequest; import io.opentdf.platform.policy.attributes.GetAttributeValuesByFqnsResponse; import io.opentdf.platform.policy.attributes.GetAttributeValuesByFqnsResponse.AttributeAndValue; @@ -31,6 +30,7 @@ import java.util.Set; import java.util.StringJoiner; import java.util.concurrent.ExecutionException; +import java.util.function.Supplier; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -106,7 +106,7 @@ public AttributeNameFQN(String url) throws AutoConfigureException { } try { - URLDecoder.decode(matcher.group(2), StandardCharsets.UTF_8.name()); + URLDecoder.decode(matcher.group(2), StandardCharsets.UTF_8); } catch (Exception e) { throw new AutoConfigureException("invalid type: error in attribute name [" + matcher.group(2) + "]"); } @@ -699,15 +699,14 @@ public static Granter newGranterFromAttributes(Value... attrValues) throws AutoC } // Gets a list of directory of KAS grants for a list of attribute FQNs - public static Granter newGranterFromService(AttributesServiceFutureStub as, KASKeyCache keyCache, AttributeValueFQN... fqns) throws AutoConfigureException, ExecutionException, InterruptedException { + public static Granter newGranterFromService(AttributesServiceClient as, KASKeyCache keyCache, AttributeValueFQN... fqns) throws AutoConfigureException, ExecutionException, InterruptedException { GetAttributeValuesByFqnsRequest request = GetAttributeValuesByFqnsRequest.newBuilder() .addAllFqns(Arrays.stream(fqns).map(AttributeValueFQN::toString).collect(Collectors.toList())) .setWithValue(AttributeValueSelector.newBuilder().setWithKeyAccessGrants(true).build()) .build(); - GetAttributeValuesByFqnsResponse av = as.getAttributeValuesByFqns(request).get(); - + GetAttributeValuesByFqnsResponse av = Helpers.getAttributeValuesByFqns(as, request); return getGranter(keyCache, new ArrayList<>(av.getFqnAttributeValuesMap().values())); } diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java b/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java index 4566c917..0d6e4e78 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java @@ -9,11 +9,9 @@ import com.nimbusds.jose.jwk.RSAKey; import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.SignedJWT; -import io.grpc.ManagedChannel; import io.grpc.StatusRuntimeException; import io.grpc.Status; import io.opentdf.platform.kas.AccessServiceClient; -import io.opentdf.platform.kas.AccessServiceGrpc; import io.opentdf.platform.kas.PublicKeyRequest; import io.opentdf.platform.kas.PublicKeyResponse; import io.opentdf.platform.kas.RewrapRequest; @@ -119,30 +117,31 @@ public KASKeyCache getKeyCache() { } private String normalizeAddress(String urlString) { - URL url; - try { - url = new URL(urlString); - } catch (MalformedURLException e) { - // if there is no protocol then they either gave us - // a correct address or one we don't know how to fix - return urlString; - } - - // otherwise we take the specified port or default - // based on whether the URL uses a scheme that - // implies TLS - int port; - if (url.getPort() == -1) { - if ("http".equals(url.getProtocol())) { - port = 80; - } else { - port = 443; - } - } else { - port = url.getPort(); - } - - return format("%s:%d", url.getHost(), port); + return urlString; +// URL url; +// try { +// url = new URL(urlString); +// } catch (MalformedURLException e) { +// // if there is no protocol then they either gave us +// // a correct address or one we don't know how to fix +// return urlString; +// } +// +// // otherwise we take the specified port or default +// // based on whether the URL uses a scheme that +// // implies TLS +// int port; +// if (url.getPort() == -1) { +// if ("http".equals(url.getProtocol())) { +// port = 80; +// } else { +// port = 443; +// } +// } else { +// port = url.getPort(); +// } +// +// return format("%s:%d", url.getHost(), port); } @Override @@ -312,21 +311,8 @@ public byte[] unwrapNanoTDF(NanoTDFType.ECCurve curve, String header, String kas private final HashMap stubs = new HashMap<>(); - private static class CacheEntry { - final ManagedChannel channel; - final AccessServiceGrpc.AccessServiceBlockingStub stub; - - private CacheEntry(ManagedChannel channel, AccessServiceGrpc.AccessServiceBlockingStub stub) { - this.channel = channel; - this.stub = stub; - } - } - // make this protected so we can test the address normalization logic synchronized AccessServiceClient getStub(String url) { - var realAddress = normalizeAddress(url); - stubs.computeIfAbsent(realAddress, channelFactory); - - return stubs.get(realAddress); + return stubs.computeIfAbsent(normalizeAddress(url), channelFactory); } } diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/SDK.java b/sdk/src/main/java/io/opentdf/platform/sdk/SDK.java index db19066e..c441559d 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/SDK.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/SDK.java @@ -1,17 +1,12 @@ package io.opentdf.platform.sdk; -import io.grpc.ClientInterceptor; -import io.grpc.ManagedChannel; -import io.opentdf.platform.authorization.AuthorizationServiceGrpc; -import io.opentdf.platform.authorization.AuthorizationServiceGrpc.AuthorizationServiceFutureStub; -import io.opentdf.platform.policy.attributes.AttributesServiceGrpc; -import io.opentdf.platform.policy.attributes.AttributesServiceGrpc.AttributesServiceFutureStub; -import io.opentdf.platform.policy.namespaces.NamespaceServiceGrpc; -import io.opentdf.platform.policy.namespaces.NamespaceServiceGrpc.NamespaceServiceFutureStub; -import io.opentdf.platform.policy.resourcemapping.ResourceMappingServiceGrpc; -import io.opentdf.platform.policy.resourcemapping.ResourceMappingServiceGrpc.ResourceMappingServiceFutureStub; -import io.opentdf.platform.policy.subjectmapping.SubjectMappingServiceGrpc; -import io.opentdf.platform.policy.subjectmapping.SubjectMappingServiceGrpc.SubjectMappingServiceFutureStub; +import com.connectrpc.Interceptor; +import com.connectrpc.impl.ProtocolClient; +import io.opentdf.platform.authorization.AuthorizationServiceClient; +import io.opentdf.platform.policy.attributes.AttributesServiceClient; +import io.opentdf.platform.policy.namespaces.NamespaceServiceClient; +import io.opentdf.platform.policy.resourcemapping.ResourceMappingServiceClient; +import io.opentdf.platform.policy.subjectmapping.SubjectMappingServiceClient; import io.opentdf.platform.sdk.nanotdf.NanoTDFType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -30,7 +25,7 @@ public class SDK implements AutoCloseable { private final Services services; private final TrustManager trustManager; - private final ClientInterceptor authInterceptor; + private final Interceptor authInterceptor; private static final Logger log = LoggerFactory.getLogger(SDK.class); @@ -66,54 +61,53 @@ byte[] unwrap(Manifest.KeyAccess keyAccess, String policy, * It extends the AutoCloseable interface, allowing for the release of resources when no longer needed. */ public interface Services extends AutoCloseable { - AuthorizationServiceFutureStub authorization(); + AttributesServiceClient attributes(); - AttributesServiceFutureStub attributes(); + NamespaceServiceClient namespaces(); - NamespaceServiceFutureStub namespaces(); + SubjectMappingServiceClient subjectMappings(); - SubjectMappingServiceFutureStub subjectMappings(); + ResourceMappingServiceClient resourceMappings(); - ResourceMappingServiceFutureStub resourceMappings(); + AuthorizationServiceClient authorization(); KAS kas(); - static Services newServices(ManagedChannel channel, KAS kas) { - var attributeService = AttributesServiceGrpc.newFutureStub(channel); - var namespaceService = NamespaceServiceGrpc.newFutureStub(channel); - var subjectMappingService = SubjectMappingServiceGrpc.newFutureStub(channel); - var resourceMappingService = ResourceMappingServiceGrpc.newFutureStub(channel); - var authorizationService = AuthorizationServiceGrpc.newFutureStub(channel); + static Services newServices(ProtocolClient client, KAS kas) { + var attributeService = new AttributesServiceClient(client); + var namespaceService = new NamespaceServiceClient(client); + var subjectMappingService = new SubjectMappingServiceClient(client); + var resourceMappingService = new ResourceMappingServiceClient(client); + var authorizationService = new AuthorizationServiceClient(client); return new Services() { @Override public void close() throws Exception { - channel.shutdownNow(); kas.close(); } @Override - public AttributesServiceFutureStub attributes() { + public AttributesServiceClient attributes() { return attributeService; } @Override - public NamespaceServiceFutureStub namespaces() { + public NamespaceServiceClient namespaces() { return namespaceService; } @Override - public SubjectMappingServiceFutureStub subjectMappings() { + public SubjectMappingServiceClient subjectMappings() { return subjectMappingService; } @Override - public ResourceMappingServiceFutureStub resourceMappings() { + public ResourceMappingServiceClient resourceMappings() { return resourceMappingService; } @Override - public AuthorizationServiceFutureStub authorization() { + public AuthorizationServiceClient authorization() { return authorizationService; } @@ -129,11 +123,11 @@ public Optional getTrustManager() { return Optional.ofNullable(trustManager); } - public Optional getAuthInterceptor() { + public Optional getAuthInterceptor() { return Optional.ofNullable(authInterceptor); } - SDK(Services services, TrustManager trustManager, ClientInterceptor authInterceptor) { + SDK(Services services, TrustManager trustManager, Interceptor authInterceptor) { this.services = services; this.trustManager = trustManager; this.authInterceptor = authInterceptor; diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java b/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java index 7002ff83..5183f04b 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java @@ -1,10 +1,12 @@ package io.opentdf.platform.sdk; +import com.connectrpc.Interceptor; import com.connectrpc.ProtocolClientConfig; -import com.connectrpc.ProtocolClientInterface; -import com.connectrpc.SerializationStrategy; +import com.connectrpc.extensions.GoogleJavaProtobufStrategy; import com.connectrpc.impl.ProtocolClient; import com.connectrpc.okhttp.ConnectOkHttpClient; +import com.connectrpc.protocols.GETConfiguration; +import com.connectrpc.protocols.NetworkProtocol; import com.nimbusds.jose.JOSEException; import com.nimbusds.jose.jwk.KeyUse; import com.nimbusds.jose.jwk.RSAKey; @@ -21,17 +23,20 @@ import com.nimbusds.oauth2.sdk.token.TokenTypeURI; import com.nimbusds.oauth2.sdk.tokenexchange.TokenExchangeGrant; import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata; -import io.grpc.*; +import io.grpc.Status; +import io.grpc.StatusRuntimeException; import io.opentdf.platform.kas.AccessServiceClient; import io.opentdf.platform.wellknownconfiguration.GetWellKnownConfigurationRequest; import io.opentdf.platform.wellknownconfiguration.GetWellKnownConfigurationResponse; -import io.opentdf.platform.wellknownconfiguration.WellKnownServiceGrpc; +import io.opentdf.platform.wellknownconfiguration.WellKnownServiceClient; import nl.altindag.ssl.SSLFactory; import nl.altindag.ssl.pem.util.PemUtils; import okhttp3.OkHttpClient; +import okhttp3.Protocol; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.annotation.Nonnull; import javax.net.ssl.TrustManager; import javax.net.ssl.X509ExtendedTrustManager; import java.io.File; @@ -40,6 +45,7 @@ import java.io.InputStream; import java.nio.file.Path; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.UUID; import java.util.function.Function; @@ -141,7 +147,7 @@ public SDKBuilder useInsecurePlaintextConnection(Boolean usePlainText) { return this; } - private GRPCAuthInterceptor getGrpcAuthInterceptor(RSAKey rsaKey) { + private Interceptor getAuthInterceptor(RSAKey rsaKey) { if (platformEndpoint == null) { throw new SDKException("cannot build an SDK without specifying the platform endpoint"); } @@ -155,21 +161,15 @@ private GRPCAuthInterceptor getGrpcAuthInterceptor(RSAKey rsaKey) { // we don't add the auth listener to this channel since it is only used to call // the // well known endpoint - ManagedChannel bootstrapChannel = null; + ProtocolClient bootstrapClient = null; GetWellKnownConfigurationResponse config; + bootstrapClient = getProtocolClient(platformEndpoint) ; + var stub = new WellKnownServiceClient(bootstrapClient); try { - bootstrapChannel = getManagedChannelBuilder(platformEndpoint).build(); - var stub = WellKnownServiceGrpc.newBlockingStub(bootstrapChannel); - try { - config = stub.getWellKnownConfiguration(GetWellKnownConfigurationRequest.getDefaultInstance()); - } catch (StatusRuntimeException e) { - Status status = Status.fromThrowable(e); - throw new SDKException(String.format("Got grpc status [%s] when getting configuration", status), e); - } - } finally { - if (bootstrapChannel != null) { - bootstrapChannel.shutdown(); - } + config = Helpers.call(stub.getWellKnownConfigurationBlocking(GetWellKnownConfigurationRequest.getDefaultInstance(), Collections.emptyMap())); + } catch (StatusRuntimeException e) { + Status status = Status.fromThrowable(e); + throw new SDKException(String.format("Got grpc status [%s] when getting configuration", status), e); } String platformIssuer; @@ -178,7 +178,6 @@ private GRPCAuthInterceptor getGrpcAuthInterceptor(RSAKey rsaKey) { .getConfiguration() .getFieldsOrThrow(PLATFORM_ISSUER) .getStringValue(); - } catch (IllegalArgumentException e) { logger.warn( "no `platform_issuer` found in well known configuration. requests from the SDK will be unauthenticated", @@ -201,17 +200,17 @@ private GRPCAuthInterceptor getGrpcAuthInterceptor(RSAKey rsaKey) { if (this.authzGrant == null) { this.authzGrant = new ClientCredentialsGrant(); } - - return new GRPCAuthInterceptor(clientAuth, rsaKey, providerMetadata.getTokenEndpointURI(), this.authzGrant, sslFactory); + var ts = new TokenSource(clientAuth, rsaKey, providerMetadata.getTokenEndpointURI(), this.authzGrant, sslFactory); + return new AuthInterceptor(ts); } static class ServicesAndInternals { - final ClientInterceptor interceptor; + final Interceptor interceptor; final TrustManager trustManager; final SDK.Services services; - ServicesAndInternals(ClientInterceptor interceptor, TrustManager trustManager, SDK.Services services) { + ServicesAndInternals(Interceptor interceptor, TrustManager trustManager, SDK.Services services) { this.interceptor = interceptor; this.trustManager = trustManager; this.services = services; @@ -229,33 +228,42 @@ ServicesAndInternals buildServices() { throw new SDKException("Error generating DPoP key", e); } - var authInterceptor = getGrpcAuthInterceptor(dpopKey); - ManagedChannel channel; - Function managedChannelFactory; - if (authInterceptor == null) { - channel = getManagedChannelBuilder(platformEndpoint).build(); - managedChannelFactory = (String endpoint) -> getManagedChannelBuilder(endpoint).build(); - - } else { - channel = getManagedChannelBuilder(platformEndpoint).intercept(authInterceptor).build(); - managedChannelFactory = (String endpoint) -> getManagedChannelBuilder(endpoint).intercept(authInterceptor) - .build(); - } - var c = new OkHttpClient.Builder() - .protocols(OkHttpClient.Companion.getDEFAULT_PROTOCOLS$okhttp()) - .connectionSpecs(OkHttpClient.Companion.getDEFAULT_CONNECTION_SPECS$okhttp()) - .socketFactory(sslFactory.getSslSocketFactory()) - .build(); + var authInterceptor = getAuthInterceptor(dpopKey); + var kasClient = getKASClient(dpopKey, authInterceptor); + var protocolClient = getProtocolClient(platformEndpoint, authInterceptor); - var as = new ProtocolClient( - new ConnectOkHttpClient(c), - new ProtocolClientConfig(platformEndpoint, ) - ); - var client = new KASClient(managedChannelFactory, dpopKey); return new ServicesAndInternals( authInterceptor, sslFactory == null ? null : sslFactory.getTrustManager().orElse(null), - SDK.Services.newServices(channel, client)); + SDK.Services.newServices(protocolClient, kasClient)); + } + + @Nonnull + private KASClient getKASClient(RSAKey dpopKey, Interceptor interceptor) { + Function clientFactory = (String endpoint) -> { + var c = new OkHttpClient.Builder(); + if (usePlainText) { + c.protocols(List.of(Protocol.H2_PRIOR_KNOWLEDGE)); + } + if (sslFactory != null) { + c.sslSocketFactory(sslFactory.getSslSocketFactory(), sslFactory.getTrustManager().get()); + } + + var as = new ProtocolClient( + new ConnectOkHttpClient(c.build()), + new ProtocolClientConfig(endpoint, + new GoogleJavaProtobufStrategy(), + NetworkProtocol.GRPC, + null, + GETConfiguration.Enabled.INSTANCE, + List.of(_ignored -> interceptor) + ) + ); + + return new AccessServiceClient(as); + }; + + return new KASClient(clientFactory, dpopKey); } public SDK build() { @@ -263,31 +271,32 @@ public SDK build() { return new SDK(services.services, services.trustManager, services.interceptor); } - /** - * This produces a channel configured with all the available SDK options. The - * only - * reason it can't take in an interceptor is because we need to create a channel - * that - * doesn't have any authentication when we are bootstrapping - * - * @param endpoint The endpoint that we are creating the channel for - * @return {@type ManagedChannelBuilder} configured with the SDK options - */ - private ManagedChannelBuilder getManagedChannelBuilder(String endpoint) { - ManagedChannelBuilder channelBuilder; - if (sslFactory != null && !usePlainText) { - channelBuilder = Grpc.newChannelBuilder(endpoint, TlsChannelCredentials.newBuilder() - .trustManager(sslFactory.getTrustManager().get()).build()); - } else { - channelBuilder = ManagedChannelBuilder.forTarget(endpoint); - } + private ProtocolClient getProtocolClient(String endpoint) { + return getProtocolClient(endpoint, null); + } + private ProtocolClient getProtocolClient(String endpoint, Interceptor interceptor) { + var httpClient = new OkHttpClient.Builder(); if (usePlainText) { - channelBuilder = channelBuilder.usePlaintext(); + httpClient.protocols(List.of(Protocol.H2_PRIOR_KNOWLEDGE)); } - return channelBuilder; + if (sslFactory != null) { + httpClient.sslSocketFactory(sslFactory.getSslSocketFactory(), sslFactory.getTrustManager().get()); + } + + var protocolClientConfig = new ProtocolClientConfig( + endpoint, + new GoogleJavaProtobufStrategy(), + NetworkProtocol.GRPC, + null, + GETConfiguration.Enabled.INSTANCE, + interceptor == null ? Collections.emptyList() : List.of((_config) -> interceptor) + ); + + return new ProtocolClient(new ConnectOkHttpClient(httpClient.build()), protocolClientConfig); } + SSLFactory getSslFactory() { return this.sslFactory; } diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java b/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java index f74b3cb6..407c5e55 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java @@ -5,7 +5,7 @@ import com.nimbusds.jose.*; import io.opentdf.platform.policy.Value; -import io.opentdf.platform.policy.attributes.AttributesServiceGrpc.AttributesServiceFutureStub; +import io.opentdf.platform.policy.attributes.AttributesServiceClient; import io.opentdf.platform.sdk.Config.TDFConfig; import io.opentdf.platform.sdk.Autoconfigure.AttributeValueFQN; import io.opentdf.platform.sdk.Config.KASInfo; @@ -475,7 +475,7 @@ private static byte[] calculateSignature(byte[] data, byte[] secret, Config.Inte public TDFObject createTDF(InputStream payload, OutputStream outputStream, - Config.TDFConfig tdfConfig, SDK.KAS kas, AttributesServiceFutureStub attrService) + Config.TDFConfig tdfConfig, SDK.KAS kas, AttributesServiceClient attributesServiceClient) throws IOException, JOSEException, AutoConfigureException, InterruptedException, ExecutionException, DecoderException { if (tdfConfig.autoconfigure) { @@ -483,7 +483,7 @@ public TDFObject createTDF(InputStream payload, if (tdfConfig.attributeValues != null && !tdfConfig.attributeValues.isEmpty()) { granter = Autoconfigure.newGranterFromAttributes(tdfConfig.attributeValues.toArray(new Value[0])); } else if (tdfConfig.attributes != null && !tdfConfig.attributes.isEmpty()) { - granter = Autoconfigure.newGranterFromService(attrService, kas.getKeyCache(), + granter = Autoconfigure.newGranterFromService(attributesServiceClient, kas.getKeyCache(), tdfConfig.attributes.toArray(new AttributeValueFQN[0])); } diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/GRPCAuthInterceptor.java b/sdk/src/main/java/io/opentdf/platform/sdk/TokenSource.java similarity index 67% rename from sdk/src/main/java/io/opentdf/platform/sdk/GRPCAuthInterceptor.java rename to sdk/src/main/java/io/opentdf/platform/sdk/TokenSource.java index a7e4e8b2..d08b55a7 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/GRPCAuthInterceptor.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/TokenSource.java @@ -1,5 +1,6 @@ package io.opentdf.platform.sdk; +import com.connectrpc.http.HTTPMethod; import com.nimbusds.jose.JOSEException; import com.nimbusds.jose.JWSAlgorithm; import com.nimbusds.jose.jwk.RSAKey; @@ -27,6 +28,7 @@ import java.net.URI; import java.net.URISyntaxException; +import java.net.URL; import java.time.Instant; /** @@ -34,25 +36,25 @@ * to the server. It adds authentication headers to the requests by fetching and caching access * tokens. */ -class GRPCAuthInterceptor implements ClientInterceptor { +class TokenSource { private Instant tokenExpiryTime; private AccessToken token; private final ClientAuthentication clientAuth; private final RSAKey rsaKey; private final URI tokenEndpointURI; private final AuthorizationGrant authzGrant; - private SSLFactory sslFactory; - private static final Logger logger = LoggerFactory.getLogger(GRPCAuthInterceptor.class); + private final SSLFactory sslFactory; + private static final Logger logger = LoggerFactory.getLogger(TokenSource.class); /** - * Constructs a new GRPCAuthInterceptor with the specified client authentication and RSA key. + * Constructs a new TokenSource with the specified client authentication and RSA key. * * @param clientAuth the client authentication to be used by the interceptor * @param rsaKey the RSA key to be used by the interceptor * @param sslFactory Optional SSLFactory for Requests */ - public GRPCAuthInterceptor(ClientAuthentication clientAuth, RSAKey rsaKey, URI tokenEndpointURI, AuthorizationGrant authzGrant, SSLFactory sslFactory) { + public TokenSource(ClientAuthentication clientAuth, RSAKey rsaKey, URI tokenEndpointURI, AuthorizationGrant authzGrant, SSLFactory sslFactory) { this.clientAuth = clientAuth; this.rsaKey = rsaKey; this.tokenEndpointURI = tokenEndpointURI; @@ -60,43 +62,43 @@ public GRPCAuthInterceptor(ClientAuthentication clientAuth, RSAKey rsaKey, URI t this.authzGrant = authzGrant; } - /** - * Intercepts the client call before it is sent to the server. - * - * @param method The method descriptor for the call. - * @param callOptions The call options for the call. - * @param next The next channel in the channel pipeline. - * @param The type of the request message. - * @param The type of the response message. - * @return A client call with the intercepted behavior. - */ - @Override - public ClientCall interceptCall(MethodDescriptor method, - CallOptions callOptions, Channel next) { - return new ForwardingClientCall.SimpleForwardingClientCall<>(next.newCall(method, callOptions)) { - @Override - public void start(Listener responseListener, Metadata headers) { - // Get the access token - AccessToken t = getToken(); - headers.put(Metadata.Key.of("Authorization", Metadata.ASCII_STRING_MARSHALLER), - "DPoP " + t.getValue()); - - // Build the DPoP proof for each request - try { - DPoPProofFactory dpopFactory = new DefaultDPoPProofFactory(rsaKey, JWSAlgorithm.RS256); - - URI uri = new URI("/" + method.getFullMethodName()); - SignedJWT proof = dpopFactory.createDPoPJWT("POST", uri, t); - headers.put(Metadata.Key.of("DPoP", Metadata.ASCII_STRING_MARSHALLER), - proof.serialize()); - } catch (URISyntaxException e) { - throw new RuntimeException("Invalid URI syntax for DPoP proof creation", e); - } catch (JOSEException e) { - throw new RuntimeException("Error creating DPoP proof", e); - } - super.start(responseListener, headers); - } - }; + class AuthHeaders { + private final String authHeader; + private final String dpopHeader; + + public AuthHeaders(String authHeader, String dpopHeader) { + this.authHeader = authHeader; + this.dpopHeader = dpopHeader; + } + + public String getAuthHeader() { + return authHeader; + } + + public String getDpopHeader() { + return dpopHeader; + } + } + + public AuthHeaders getAuthHeaders(URL url, String method) { + // Get the access token + AccessToken t = getToken(); + + // Build the DPoP proof for each request + String dpopProof; + try { + DPoPProofFactory dpopFactory = new DefaultDPoPProofFactory(rsaKey, JWSAlgorithm.RS256); + SignedJWT proof = dpopFactory.createDPoPJWT(method, url.toURI(), t); + dpopProof = proof.serialize(); + } catch (URISyntaxException e) { + throw new RuntimeException("Invalid URI syntax for DPoP proof creation", e); + } catch (JOSEException e) { + throw new RuntimeException("Error creating DPoP proof", e); + } + + return new AuthHeaders( + "DPoP " + t.getValue(), + dpopProof); } /** diff --git a/sdk/src/main/kotlin/io/opentdf/platform/AuthInterceptor.kt b/sdk/src/main/kotlin/io/opentdf/platform/AuthInterceptor.kt new file mode 100644 index 00000000..5da64919 --- /dev/null +++ b/sdk/src/main/kotlin/io/opentdf/platform/AuthInterceptor.kt @@ -0,0 +1,58 @@ +package io.opentdf.platform.sdk + +import com.connectrpc.Interceptor +import com.connectrpc.StreamFunction +import com.connectrpc.UnaryFunction +import com.connectrpc.http.UnaryHTTPRequest +import com.connectrpc.http.clone + +private class AuthInterceptor(private val ts: TokenSource) : Interceptor{ + override fun streamFunction(): StreamFunction { + return StreamFunction( + requestFunction = { request -> + val requestHeaders = mutableMapOf>() + val authHeaders = ts.getAuthHeaders(request.url, "POST"); + requestHeaders["Authorization"] = listOf(authHeaders.authHeader) + requestHeaders["DPoP"] = listOf(authHeaders.dpopHeader) + + return@StreamFunction request.clone( + url = request.url, + contentType = request.contentType, + headers = requestHeaders, + timeout = request.timeout, + methodSpec = request.methodSpec, + ) + }, + requestBodyFunction = { resp -> + resp + }, + streamResultFunction = { streamResult -> + streamResult + }, + ) + } + + override fun unaryFunction(): UnaryFunction { + return UnaryFunction( + requestFunction = { request -> + val requestHeaders = mutableMapOf>() + val authHeaders = ts.getAuthHeaders(request.url, request.httpMethod.name); + requestHeaders["Authorization"] = listOf(authHeaders.authHeader) + requestHeaders["DPoP"] = listOf(authHeaders.dpopHeader) + + return@UnaryFunction UnaryHTTPRequest( + url = request.url, + contentType = request.contentType, + headers = requestHeaders, + message = request.message, + timeout = request.timeout, + methodSpec = request.methodSpec, + httpMethod = request.httpMethod + ) + }, + responseFunction = { resp -> + resp + }, + ) + } +} \ No newline at end of file diff --git a/sdk/src/main/kotlin/io/opentdf/platform/Helpers.kt b/sdk/src/main/kotlin/io/opentdf/platform/Helpers.kt new file mode 100644 index 00000000..e4b0dbf0 --- /dev/null +++ b/sdk/src/main/kotlin/io/opentdf/platform/Helpers.kt @@ -0,0 +1,36 @@ +package io.opentdf.platform.sdk + +import com.connectrpc.UnaryBlockingCall +import com.connectrpc.getOrThrow +import io.opentdf.platform.policy.attributes.AttributesServiceClient +import io.opentdf.platform.policy.attributes.GetAttributeValuesByFqnsRequest +import io.opentdf.platform.policy.attributes.GetAttributeValuesByFqnsResponse +import io.opentdf.platform.policy.namespaces.GetNamespaceRequest +import io.opentdf.platform.policy.namespaces.NamespaceServiceClient +import kotlinx.coroutines.runBlocking + +class Helpers { + companion object { + @JvmStatic + fun call(ub: UnaryBlockingCall): T { + return ub.execute().getOrThrow(); + } + + @JvmStatic + fun noHeaders(): Map { + return emptyMap() + } + + @JvmStatic + fun getNamespace(nsc: NamespaceServiceClient, req: GetNamespaceRequest) { + return runBlocking { + nsc.getNamespace(req, emptyMap()).getOrThrow(); + } + } + + @JvmStatic + fun getAttributeValuesByFqns(nsc: AttributesServiceClient, req: GetAttributeValuesByFqnsRequest): GetAttributeValuesByFqnsResponse { + return nsc.getAttributeValuesByFqnsBlocking(req, emptyMap()).execute().getOrThrow(); + } + } +} \ No newline at end of file diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/AutoconfigureTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/AutoconfigureTest.java index bb9faf18..883115e9 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/AutoconfigureTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/AutoconfigureTest.java @@ -1,934 +1,945 @@ -package io.opentdf.platform.sdk; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.lenient; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; - -import io.opentdf.platform.policy.Attribute; -import io.opentdf.platform.policy.Value; -import io.opentdf.platform.policy.attributes.AttributesServiceGrpc; -import io.opentdf.platform.policy.attributes.GetAttributeValuesByFqnsRequest; -import io.opentdf.platform.policy.attributes.GetAttributeValuesByFqnsResponse; -import io.opentdf.platform.sdk.Autoconfigure.AttributeValueFQN; -import io.opentdf.platform.sdk.Autoconfigure.Granter.AttributeBooleanExpression; -import io.opentdf.platform.sdk.Autoconfigure.Granter.BooleanKeyExpression; -import io.opentdf.platform.sdk.Autoconfigure.KeySplitStep; -import io.opentdf.platform.sdk.Autoconfigure.Granter; -import io.opentdf.platform.policy.Namespace; -import io.opentdf.platform.policy.PublicKey; -import io.opentdf.platform.policy.KeyAccessServer; -import io.opentdf.platform.policy.AttributeRuleTypeEnum; -import io.opentdf.platform.policy.KasPublicKey; -import io.opentdf.platform.policy.KasPublicKeyAlgEnum; -import io.opentdf.platform.policy.KasPublicKeySet; - -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.mockito.Mockito; - -import com.google.common.util.concurrent.SettableFuture; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.concurrent.ExecutionException; -import java.util.stream.Collectors; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class AutoconfigureTest { - - private static final String KAS_AU = "https://kas.au/"; - private static final String KAS_CA = "https://kas.ca/"; - private static final String KAS_UK = "https://kas.uk/"; - private static final String KAS_NZ = "https://kas.nz/"; - private static final String KAS_US = "https://kas.us/"; - private static final String KAS_US_HCS = "https://hcs.kas.us/"; - private static final String KAS_US_SA = "https://si.kas.us/"; - public static final String SPECIFIED_KAS = "https://attr.kas.com/"; - public static final String EVEN_MORE_SPECIFIC_KAS = "https://value.kas.com/"; - private static final String NAMESPACE_KAS = "https://namespace.kas.com/"; - private static Autoconfigure.AttributeNameFQN SPKSPECKED; - private static Autoconfigure.AttributeNameFQN SPKUNSPECKED; - - private static Autoconfigure.AttributeNameFQN CLS; - private static Autoconfigure.AttributeNameFQN N2K; - private static Autoconfigure.AttributeNameFQN REL; - private static Autoconfigure.AttributeNameFQN UNSPECKED; - private static Autoconfigure.AttributeNameFQN SPECKED; - - private static Autoconfigure.AttributeValueFQN clsA; - private static Autoconfigure.AttributeValueFQN clsS; - private static Autoconfigure.AttributeValueFQN clsTS; - private static Autoconfigure.AttributeValueFQN n2kHCS; - private static Autoconfigure.AttributeValueFQN n2kInt; - private static Autoconfigure.AttributeValueFQN n2kSI; - private static Autoconfigure.AttributeValueFQN rel2can; - private static Autoconfigure.AttributeValueFQN rel2gbr; - private static Autoconfigure.AttributeValueFQN rel2nzl; - private static Autoconfigure.AttributeValueFQN rel2usa; - private static Autoconfigure.AttributeValueFQN uns2uns; - private static Autoconfigure.AttributeValueFQN uns2spk; - private static Autoconfigure.AttributeValueFQN spk2uns; - private static Autoconfigure.AttributeValueFQN spk2spk; - private static AttributeValueFQN spk2uns2uns; - private static AttributeValueFQN spk2uns2spk; - private static Autoconfigure.AttributeValueFQN spk2spk2uns; - private static Autoconfigure.AttributeValueFQN spk2spk2spk; - - @BeforeAll - public static void setup() throws AutoConfigureException { - // Initialize the FQNs (Fully Qualified Names) - CLS = new Autoconfigure.AttributeNameFQN("https://virtru.com/attr/Classification"); - N2K = new Autoconfigure.AttributeNameFQN("https://virtru.com/attr/Need%20to%20Know"); - REL = new Autoconfigure.AttributeNameFQN("https://virtru.com/attr/Releasable%20To"); - UNSPECKED = new Autoconfigure.AttributeNameFQN("https://other.com/attr/unspecified"); - SPECKED = new Autoconfigure.AttributeNameFQN("https://other.com/attr/specified"); - SPKUNSPECKED = new Autoconfigure.AttributeNameFQN("https://hasgrants.com/attr/unspecified"); - SPKSPECKED = new Autoconfigure.AttributeNameFQN("https://hasgrants.com/attr/specified"); - - clsA = new Autoconfigure.AttributeValueFQN("https://virtru.com/attr/Classification/value/Allowed"); - clsS = new Autoconfigure.AttributeValueFQN("https://virtru.com/attr/Classification/value/Secret"); - clsTS = new Autoconfigure.AttributeValueFQN("https://virtru.com/attr/Classification/value/Top%20Secret"); - - n2kHCS = new Autoconfigure.AttributeValueFQN("https://virtru.com/attr/Need%20to%20Know/value/HCS"); - n2kInt = new Autoconfigure.AttributeValueFQN("https://virtru.com/attr/Need%20to%20Know/value/INT"); - n2kSI = new Autoconfigure.AttributeValueFQN("https://virtru.com/attr/Need%20to%20Know/value/SI"); - - rel2can = new Autoconfigure.AttributeValueFQN("https://virtru.com/attr/Releasable%20To/value/CAN"); - rel2gbr = new Autoconfigure.AttributeValueFQN("https://virtru.com/attr/Releasable%20To/value/GBR"); - rel2nzl = new Autoconfigure.AttributeValueFQN("https://virtru.com/attr/Releasable%20To/value/NZL"); - rel2usa = new Autoconfigure.AttributeValueFQN("https://virtru.com/attr/Releasable%20To/value/USA"); - - uns2uns = new Autoconfigure.AttributeValueFQN("https://other.com/attr/unspecified/value/unspecked"); - uns2spk = new Autoconfigure.AttributeValueFQN("https://other.com/attr/unspecified/value/specked"); - spk2uns = new Autoconfigure.AttributeValueFQN("https://other.com/attr/specified/value/unspecked"); - spk2spk = new Autoconfigure.AttributeValueFQN("https://other.com/attr/specified/value/specked"); - - spk2uns2uns = new Autoconfigure.AttributeValueFQN("https://hasgrants.com/attr/unspecified/value/unspecked"); - spk2uns2spk = new Autoconfigure.AttributeValueFQN("https://hasgrants.com/attr/unspecified/value/specked"); - spk2spk2uns = new Autoconfigure.AttributeValueFQN("https://hasgrants.com/attr/specified/value/unspecked"); - spk2spk2spk = new Autoconfigure.AttributeValueFQN("https://hasgrants.com/attr/specified/value/specked"); - } - - private static String spongeCase(String s) { - final Pattern PATTERN = Pattern.compile("^(https?://[\\w./]+/attr/)([^/]*)(/value/)?(\\S*)?$"); - Matcher matcher = PATTERN.matcher(s); - - if (!matcher.matches()) { - throw new IllegalArgumentException("Invalid input string"); - } - - StringBuilder sb = new StringBuilder(); - sb.append(matcher.group(1)); - - String n = matcher.group(2); - for (int i = 0; i < n.length(); i++) { - String sub = n.substring(i, i + 1); - if ((i & 1) == 1) { - sb.append(sub.toUpperCase()); - } else { - sb.append(sub); - } - } - - if (matcher.group(3) != null) { - sb.append(matcher.group(3)); - String v = matcher.group(4); - for (int i = 0; i < v.length(); i++) { - String sub = v.substring(i, i + 1); - if ((i & 1) == 1) { - sb.append(sub); - } else { - sb.append(sub.toUpperCase()); - } - } - } - return sb.toString(); - } - - private List valuesToPolicy(AttributeValueFQN... p) throws AutoConfigureException { - List values = new ArrayList<>(); - for (AttributeValueFQN afqn : List.of(p)) { - values.add(mockValueFor(afqn)); - } - return values; - } - - private List policyToStringKeys(List policy) { - return policy.stream() - .map(AttributeValueFQN::getKey) - .collect(Collectors.toList()); - } - - private Autoconfigure.AttributeValueFQN messUpV(Autoconfigure.AttributeValueFQN a) { - try { - return new Autoconfigure.AttributeValueFQN(spongeCase(a.toString())); - } catch (Exception e) { - throw new RuntimeException("Failed to create AttributeValueFQN", e); - } - } - - private Attribute mockAttributeFor(Autoconfigure.AttributeNameFQN fqn) { - Namespace ns1 = Namespace.newBuilder().setId("v").setName("virtru.com").setFqn("https://virtru.com").build(); - Namespace ns2 = Namespace.newBuilder().setId("o").setName("other.com").setFqn("https://other.com").build(); - Namespace ns3 = Namespace.newBuilder().setId("h").setName("hasgrants.com").addGrants(KeyAccessServer.newBuilder().setUri(NAMESPACE_KAS).build()).setFqn("https://hasgrants.com").build(); - - String key = fqn.getKey(); - if (key.equals(CLS.getKey())) { - return Attribute.newBuilder().setId("CLS").setNamespace(ns1) - .setName("Classification").setRule(AttributeRuleTypeEnum.ATTRIBUTE_RULE_TYPE_ENUM_HIERARCHY) - .setFqn(fqn.toString()).build(); - } else if (key.equals(N2K.getKey())) { - return Attribute.newBuilder().setId("N2K").setNamespace(ns1) - .setName("Need to Know").setRule(AttributeRuleTypeEnum.ATTRIBUTE_RULE_TYPE_ENUM_ALL_OF) - .setFqn(fqn.toString()).build(); - } else if (key.equals(REL.getKey())) { - return Attribute.newBuilder().setId("REL").setNamespace(ns1) - .setName("Releasable To").setRule(AttributeRuleTypeEnum.ATTRIBUTE_RULE_TYPE_ENUM_ANY_OF) - .setFqn(fqn.toString()).build(); - } else if (key.equals(SPECKED.getKey())) { - return Attribute.newBuilder().setId("SPK").setNamespace(ns2) - .setName("specified").setRule(AttributeRuleTypeEnum.ATTRIBUTE_RULE_TYPE_ENUM_ANY_OF) - .setFqn(fqn.toString()) - .addGrants(KeyAccessServer.newBuilder().setUri(SPECIFIED_KAS).build()) - .build(); - } else if (key.equals(UNSPECKED.getKey())) { - return Attribute.newBuilder().setId("UNS").setNamespace(ns2) - .setName("unspecified").setRule(AttributeRuleTypeEnum.ATTRIBUTE_RULE_TYPE_ENUM_ANY_OF) - .setFqn(fqn.toString()).build(); - } else if (key.equals(SPKSPECKED.getKey())) { - return Attribute.newBuilder().setId("SPKSPK").setNamespace(ns3) - .setName("specified").setRule(AttributeRuleTypeEnum.ATTRIBUTE_RULE_TYPE_ENUM_ANY_OF) - .addGrants(KeyAccessServer.newBuilder().setUri(SPECIFIED_KAS).build()) - .setName(fqn.toString()) - .build(); - } else if (key.equals(SPKUNSPECKED.getKey())) { - return Attribute.newBuilder().setId("SPKUNSPK").setNamespace(ns3) - .setName("unspecified").setRule(AttributeRuleTypeEnum.ATTRIBUTE_RULE_TYPE_ENUM_ANY_OF) - .setName(fqn.toString()) - .build(); - } - - throw new IllegalArgumentException("Key not recognized: " + key); - } - - private Value mockValueFor(Autoconfigure.AttributeValueFQN fqn) throws AutoConfigureException { - Autoconfigure.AttributeNameFQN an = fqn.prefix(); - Attribute a = mockAttributeFor(an); - String v = fqn.value(); - Value p = Value.newBuilder() - .setId(a.getId() + ":" + v) - .setAttribute(a) - .setValue(v) - .setFqn(fqn.toString()) - .build(); - - if (an.getKey().equals(N2K.getKey())) { - switch (v.toUpperCase()) { - case "INT": - p = p.toBuilder().addGrants(KeyAccessServer.newBuilder().setUri(KAS_UK).build()).build(); - break; - case "HCS": - p = p.toBuilder().addGrants(KeyAccessServer.newBuilder().setUri(KAS_US_HCS).build()).build(); - break; - case "SI": - p = p.toBuilder().addGrants(KeyAccessServer.newBuilder().setUri(KAS_US_SA).build()).build(); - break; - } - } else if (an.getKey().equals(REL.getKey())) { - switch (v.toUpperCase()) { - case "FVEY": - p = p.toBuilder().addGrants(KeyAccessServer.newBuilder().setUri(KAS_AU).build()) - .addGrants(1, KeyAccessServer.newBuilder().setUri(KAS_CA).build()) - .addGrants(1, KeyAccessServer.newBuilder().setUri(KAS_UK).build()) - .addGrants(1, KeyAccessServer.newBuilder().setUri(KAS_NZ).build()) - .addGrants(1, KeyAccessServer.newBuilder().setUri(KAS_US).build()) - .build(); - break; - case "AUS": - p = p.toBuilder().addGrants(KeyAccessServer.newBuilder().setUri(KAS_AU).build()) - .build(); - break; - case "CAN": - p = p.toBuilder().addGrants(0, KeyAccessServer.newBuilder().setUri(KAS_CA).build()) - .build(); - break; - case "GBR": - p = p.toBuilder().addGrants(KeyAccessServer.newBuilder().setUri(KAS_UK).build()) - .build(); - break; - case "NZL": - p = p.toBuilder().addGrants(KeyAccessServer.newBuilder().setUri(KAS_NZ).build()) - .build(); - break; - case "USA": - p = p.toBuilder().addGrants(KeyAccessServer.newBuilder().setUri(KAS_US).build()) - .build(); - break; - } - } else if (an.getKey().equals(CLS.getKey())) { - // defaults only - } else if (List.of(SPECKED.getKey(), SPKSPECKED.getKey()).contains(an.getKey())) { - if (fqn.value().equalsIgnoreCase("specked")) { - p = p.toBuilder().addGrants(KeyAccessServer.newBuilder().setUri(EVEN_MORE_SPECIFIC_KAS).build()) - .build(); - } - } else if (List.of(UNSPECKED.getKey(), SPKUNSPECKED.getKey()).contains(an.getKey())) { - if (fqn.value().equalsIgnoreCase("specked")) { - p = p.toBuilder().addGrants(KeyAccessServer.newBuilder().setUri(EVEN_MORE_SPECIFIC_KAS).build()) - .build(); - } - } - return p; - } - - @Test - public void testAttributeFromURL() throws AutoConfigureException { - for (TestCase tc : List.of( - new TestCase("letter", "https://e/attr/a", "https://e", "a"), - new TestCase("number", "https://e/attr/1", "https://e", "1"), - new TestCase("emoji", "https://e/attr/%F0%9F%98%81", "https://e", "😁"), - new TestCase("dash", "https://a-b.com/attr/b-c", "https://a-b.com", "b-c"))) { - Autoconfigure.AttributeNameFQN a = new Autoconfigure.AttributeNameFQN(tc.getU()); - assertThat(a.authority()).isEqualTo(tc.getAuth()); - assertThat(a.name()).isEqualTo(tc.getName()); - } - } - - @Test - public void testAttributeFromMalformedURL() { - for (TestCase tc : List.of( - new TestCase("no name", "https://e/attr"), - new TestCase("invalid prefix 1", "hxxp://e/attr/a"), - new TestCase("invalid prefix 2", "e/attr/a"), - new TestCase("invalid prefix 3", "file://e/attr/a"), - new TestCase("invalid prefix 4", "https:///attr/a"), - new TestCase("bad encoding", "https://a/attr/%😁"), - new TestCase("with value", "https://e/attr/a/value/b"))) { - assertThatThrownBy(() -> new Autoconfigure.AttributeNameFQN(tc.getU())) - .isInstanceOf(AutoConfigureException.class); - } - } - - @Test - public void testAttributeValueFromURL() { - List testCases = List.of( - new TestCase("number", "https://e/attr/a/value/1", "https://e", "a", "1"), - new TestCase("space", "https://e/attr/a/value/%20", "https://e", "a", " "), - new TestCase("emoji", "https://e/attr/a/value/%F0%9F%98%81", "https://e", "a", "😁"), - new TestCase("numberdef", "https://e/attr/1/value/one", "https://e", "1", "one"), - new TestCase("valuevalue", "https://e/attr/value/value/one", "https://e", "value", "one"), - new TestCase("dash", "https://a-b.com/attr/b-c/value/c-d", "https://a-b.com", "b-c", "c-d")); - - for (TestCase tc : testCases) { - assertDoesNotThrow(() -> { - AttributeValueFQN a = new AttributeValueFQN(tc.getU()); - assertThat(a.authority()).isEqualTo(tc.getAuth()); - assertThat(a.name()).isEqualTo(tc.getName()); - assertThat(a.value()).isEqualTo(tc.getValue()); - }); - } - } - - @Test - public void testAttributeValueFromMalformedURL() { - List testCases = List.of( - new TestCase("no name", "https://e/attr/value/1"), - new TestCase("no value", "https://e/attr/who/value"), - new TestCase("invalid prefix 1", "hxxp://e/attr/a/value/1"), - new TestCase("invalid prefix 2", "e/attr/a/a/value/1"), - new TestCase("bad encoding", "https://a/attr/emoji/value/%😁")); - - for (TestCase tc : testCases) { - assertThatThrownBy(() -> new AttributeValueFQN(tc.getU())) - .isInstanceOf(AutoConfigureException.class) - .hasMessageContaining("invalid type"); - } - } - - @Test - public void testConfigurationServicePutGet() { - List testCases = List.of( - new ConfigurationTestCase("default", List.of(clsA), 1, List.of()), - new ConfigurationTestCase("one-country", List.of(rel2gbr), 1, List.of(KAS_UK)), - new ConfigurationTestCase("two-country", List.of(rel2gbr, rel2nzl), 2, List.of(KAS_UK, KAS_NZ)), - new ConfigurationTestCase("with-default", List.of(clsA, rel2gbr), 2, List.of(KAS_UK)), - new ConfigurationTestCase("need-to-know", List.of(clsTS, rel2usa, n2kSI), 3, - List.of(KAS_US, KAS_US_SA))); - - for (ConfigurationTestCase tc : testCases) { - assertDoesNotThrow(() -> { - List v = valuesToPolicy(tc.getPolicy().toArray(new AttributeValueFQN[0])); - Granter grants = Autoconfigure.newGranterFromAttributes(v.toArray(new Value[0])); - assertThat(grants).isNotNull(); - assertThat(grants.getGrants()).hasSize(tc.getSize()); - assertThat(policyToStringKeys(tc.getPolicy())).containsAll(grants.getGrants().keySet()); - Set actualKases = new HashSet<>(); - for (Autoconfigure.KeyAccessGrant g : grants.getGrants().values()) { - assertThat(g).isNotNull(); - for (String k : g.kases) { - actualKases.add(k); - } - } - - String[] kasArray = tc.getKases().toArray(new String[tc.getKases().size()]); - assertThat(actualKases).containsExactlyInAnyOrder(kasArray); - }); - } - } - - @Test - public void testReasonerConstructAttributeBoolean() { - List testCases = List.of( - new ReasonerTestCase( - "one actual with default", - List.of(clsS, rel2can), - List.of(KAS_US), - "https://virtru.com/attr/Classification/value/Secret&https://virtru.com/attr/Releasable%20To/value/CAN", - "[DEFAULT]&(https://kas.ca/)", - "(https://kas.ca/)", - List.of(new KeySplitStep(KAS_CA, ""))), - new ReasonerTestCase( - "one defaulted attr", - List.of(clsS), - List.of(KAS_US), - "https://virtru.com/attr/Classification/value/Secret", - "[DEFAULT]", - "", - List.of(new KeySplitStep(KAS_US, ""))), - new ReasonerTestCase( - "empty policy", - List.of(), - List.of(KAS_US), - "∅", - "", - "", - List.of(new KeySplitStep(KAS_US, ""))), - new ReasonerTestCase( - "old school splits", - List.of(), - List.of(KAS_AU, KAS_CA, KAS_US), - "∅", - "", - "", - List.of(new KeySplitStep(KAS_AU, "1"), new KeySplitStep(KAS_CA, "2"), - new KeySplitStep(KAS_US, "3"))), - new ReasonerTestCase( - "simple with all three ops", - List.of(clsS, rel2gbr, n2kInt), - List.of(KAS_US), - "https://virtru.com/attr/Classification/value/Secret&https://virtru.com/attr/Releasable%20To/value/GBR&https://virtru.com/attr/Need%20to%20Know/value/INT", - "[DEFAULT]&(https://kas.uk/)&(https://kas.uk/)", - "(https://kas.uk/)", - List.of(new KeySplitStep(KAS_UK, ""))), - new ReasonerTestCase( - "compartments", - List.of(clsS, rel2gbr, rel2usa, n2kHCS, n2kSI), - List.of(KAS_US), - "https://virtru.com/attr/Classification/value/Secret&https://virtru.com/attr/Releasable%20To/value/{GBR,USA}&https://virtru.com/attr/Need%20to%20Know/value/{HCS,SI}", - "[DEFAULT]&(https://kas.uk/⋁https://kas.us/)&(https://hcs.kas.us/⋀https://si.kas.us/)", - "(https://kas.uk/⋁https://kas.us/)&(https://hcs.kas.us/)&(https://si.kas.us/)", - List.of(new KeySplitStep(KAS_UK, "1"), new KeySplitStep(KAS_US, "1"), - new KeySplitStep(KAS_US_HCS, "2"), new KeySplitStep(KAS_US_SA, "3"))), - new ReasonerTestCase( - "compartments - case insensitive", - List.of( - messUpV(clsS), messUpV(rel2gbr), messUpV(rel2usa), messUpV(n2kHCS), messUpV(n2kSI)), - List.of(KAS_US), - "https://virtru.com/attr/Classification/value/Secret&https://virtru.com/attr/Releasable%20To/value/{GBR,USA}&https://virtru.com/attr/Need%20to%20Know/value/{HCS,SI}", - "[DEFAULT]&(https://kas.uk/⋁https://kas.us/)&(https://hcs.kas.us/⋀https://si.kas.us/)", - "(https://kas.uk/⋁https://kas.us/)&(https://hcs.kas.us/)&(https://si.kas.us/)", - List.of(new KeySplitStep(KAS_UK, "1"), new KeySplitStep(KAS_US, "1"), - new KeySplitStep(KAS_US_HCS, "2"), new KeySplitStep(KAS_US_SA, "3")))); - - for (ReasonerTestCase tc : testCases) { - Granter reasoner = Autoconfigure.newGranterFromAttributes( - valuesToPolicy(tc.getPolicy().toArray(new AttributeValueFQN[0])).toArray(new Value[0])); - assertThat(reasoner).isNotNull(); - - AttributeBooleanExpression actualAB = reasoner.constructAttributeBoolean(); - assertThat(actualAB.toString().toLowerCase()).isEqualTo(tc.getAts().toLowerCase()); - - BooleanKeyExpression actualKeyed = reasoner.insertKeysForAttribute(actualAB); - assertThat(actualKeyed.toString()).isEqualTo(tc.getKeyed()); - - String reduced = actualKeyed.reduce().toString(); - assertThat(reduced).isEqualTo(tc.getReduced()); - - var wrapper = new Object() { - int i = 0; - }; - List plan = reasoner.plan(tc.getDefaults(), () -> { - return String.valueOf(wrapper.i++ + 1); - } - - ); - assertThat(plan).isEqualTo(tc.getPlan()); - } - } - - GetAttributeValuesByFqnsResponse getResponse(GetAttributeValuesByFqnsRequest req) { - GetAttributeValuesByFqnsResponse.Builder builder = GetAttributeValuesByFqnsResponse.newBuilder(); - - for (String v : req.getFqnsList()) { - AttributeValueFQN vfqn; - try { - vfqn = new AttributeValueFQN(v); - } catch (Exception e) { - return null; // Or throw the exception as needed - } - - Value val = mockValueFor(vfqn); - - builder.putFqnAttributeValues(v, GetAttributeValuesByFqnsResponse.AttributeAndValue.newBuilder() - .setAttribute(val.getAttribute()) - .setValue(val) - .build()); - } - - return builder.build(); - } - - @Test - public void testReasonerSpecificity() { - List testCases = List.of( - new ReasonerTestCase( - "uns.uns => default", - List.of(uns2uns), - List.of(KAS_US), - List.of(new KeySplitStep(KAS_US, ""))), - new ReasonerTestCase( - "uns.spk => spk", - List.of(uns2spk), - List.of(KAS_US), - List.of(new KeySplitStep(EVEN_MORE_SPECIFIC_KAS, ""))), - new ReasonerTestCase( - "spk.uns => spk", - List.of(spk2uns), - List.of(KAS_US), - List.of(new KeySplitStep(SPECIFIED_KAS, ""))), - new ReasonerTestCase( - "spk.spk => value.spk", - List.of(spk2spk), - List.of(KAS_US), - List.of(new KeySplitStep(EVEN_MORE_SPECIFIC_KAS, ""))), - new ReasonerTestCase( - "spk.spk & spk.uns => value.spk || attr.spk", - List.of(spk2spk, spk2uns), - List.of(KAS_US), - List.of(new KeySplitStep(EVEN_MORE_SPECIFIC_KAS, "1"), new KeySplitStep(SPECIFIED_KAS, "1"))), - new ReasonerTestCase( - "spk.uns & spk.spk => value.spk || attr.spk", - List.of(spk2uns, spk2spk), - List.of(KAS_US), - List.of(new KeySplitStep(SPECIFIED_KAS, "1"), new KeySplitStep(EVEN_MORE_SPECIFIC_KAS, "1"))), - new ReasonerTestCase( - "uns.spk & spk.spk => value.spk", - List.of(spk2spk, uns2spk), - List.of(KAS_US), - List.of(new KeySplitStep(EVEN_MORE_SPECIFIC_KAS, ""))), - new ReasonerTestCase( - "uns.spk & uns.uns => spk", - List.of(uns2spk, uns2uns), - List.of(KAS_US), - List.of(new KeySplitStep(EVEN_MORE_SPECIFIC_KAS, ""))), - new ReasonerTestCase( - "uns.uns & uns.spk => spk", - List.of(uns2uns, uns2spk), - List.of(KAS_US), - List.of(new KeySplitStep(EVEN_MORE_SPECIFIC_KAS, ""))), - new ReasonerTestCase( - "uns.uns & uns.spk => spk", - List.of(uns2uns, spk2spk), - List.of(KAS_US), - List.of(new KeySplitStep(EVEN_MORE_SPECIFIC_KAS, ""))), - new ReasonerTestCase( - "spk.uns.uns => ns.spk", - List.of(spk2uns2uns, uns2uns), - List.of(KAS_US), - List.of(new KeySplitStep(NAMESPACE_KAS, ""))), - new ReasonerTestCase( - "spk.uns.uns & uns.uns => ns.spk", - List.of(spk2uns2uns, uns2uns), - List.of(KAS_US), - List.of(new KeySplitStep(NAMESPACE_KAS, ""))), - new ReasonerTestCase( - "spk.uns.uns & uns.spk => ns.spk && spk", - List.of(spk2uns2uns, uns2spk), - List.of(KAS_US), - List.of(new KeySplitStep(NAMESPACE_KAS, "1"), new KeySplitStep(EVEN_MORE_SPECIFIC_KAS, "2"))), - new ReasonerTestCase( - "spk.uns.uns & spk.spk.uns && spk.uns.spk => ns.spk || attr.spk || value.spk", - List.of(spk2uns2uns, spk2spk2uns, spk2uns2spk), - List.of(KAS_US), - List.of(new KeySplitStep(NAMESPACE_KAS, "1"), new KeySplitStep(EVEN_MORE_SPECIFIC_KAS, "1"), new KeySplitStep(SPECIFIED_KAS, "2"))) - ); - - for (ReasonerTestCase tc : testCases) { - assertDoesNotThrow(() -> { - AttributesServiceGrpc.AttributesServiceFutureStub attributeGrpcStub = mock( - AttributesServiceGrpc.AttributesServiceFutureStub.class); - lenient().when(attributeGrpcStub.getAttributeValuesByFqns(any(GetAttributeValuesByFqnsRequest.class))) - .thenAnswer( - invocation -> { - GetAttributeValuesByFqnsResponse resp = getResponse( - (GetAttributeValuesByFqnsRequest) invocation.getArguments()[0]); - SettableFuture future = SettableFuture.create(); - future.set(resp); // Set the request as the future's result - return future; - }); - - Granter reasoner = Autoconfigure.newGranterFromService(attributeGrpcStub, new KASKeyCache(), - tc.getPolicy().toArray(new AttributeValueFQN[0])); - assertThat(reasoner).isNotNull(); - - var wrapper = new Object() { - int i = 0; - }; - List plan = reasoner.plan(tc.getDefaults(), () -> { - return String.valueOf(wrapper.i++ + 1); - } - - ); - assertThat(plan).hasSameElementsAs(tc.getPlan()); - }); - } - } - - private static class TestCase { - private final String n; - private final String u; - private final String auth; - private final String name; - private final String value; - - TestCase(String n, String u, String auth, String name, String value) { - this.n = n; - this.u = u; - this.auth = auth; - this.name = name; - this.value = value; - } - - TestCase(String n, String u, String auth, String name) { - this(n, u, auth, name, null); - } - - TestCase(String n, String u) { - this(n, u, null, null, null); - } - - public String getU() { - return u; - } - - public String getAuth() { - return auth; - } - - public String getName() { - return name; - } - - public String getValue() { - return value; - } - - public Autoconfigure.AttributeValueFQN getA() throws AutoConfigureException { - return new Autoconfigure.AttributeValueFQN(u); - } - } - - private static class ConfigurationTestCase { - private final String name; - private final List policy; - private final int size; - private final List kases; - - ConfigurationTestCase(String name, List policy, int size, List kases) { - this.name = name; - this.policy = policy; - this.size = size; - this.kases = kases; - } - - public String getN() { - return name; - } - - public List getPolicy() { - return policy; - } - - public int getSize() { - return size; - } - - public List getKases() { - return kases; - } - } - - private static class ReasonerTestCase { - private final String name; - private final List policy; - private final List defaults; - private final String ats; - private final String keyed; - private final String reduced; - private final List plan; - - ReasonerTestCase(String name, List policy, List defaults, String ats, String keyed, - String reduced, List plan) { - this.name = name; - this.policy = policy; - this.defaults = defaults; - this.ats = ats; - this.keyed = keyed; - this.reduced = reduced; - this.plan = plan; - } - - ReasonerTestCase(String name, List policy, List defaults, List plan) { - this.name = name; - this.policy = policy; - this.defaults = defaults; - this.plan = plan; - this.ats = null; - this.keyed = null; - this.reduced = null; - } - - public String getN() { - return name; - } - - public List getPolicy() { - return policy; - } - - public List getDefaults() { - return defaults; - } - - public String getAts() { - return ats; - } - - public String getKeyed() { - return keyed; - } - - public String getReduced() { - return reduced; - } - - public List getPlan() { - return plan; - } - } - - @Test - void testStoreKeysToCache_NoKeys() { - KASKeyCache keyCache = Mockito.mock(KASKeyCache.class); - KeyAccessServer kas1 = KeyAccessServer.newBuilder().setPublicKey( - PublicKey.newBuilder().setCached( - KasPublicKeySet.newBuilder())) - .build(); - - List kases = List.of(kas1); - - Autoconfigure.storeKeysToCache(kases, keyCache); - - verify(keyCache, never()).store(any(Config.KASInfo.class)); - } - - @Test - void testStoreKeysToCache_WithKeys() { - // Create a real KASKeyCache instance instead of mocking it - KASKeyCache keyCache = new KASKeyCache(); - - // Create the KasPublicKey object - KasPublicKey kasPublicKey1 = KasPublicKey.newBuilder() - .setAlg(KasPublicKeyAlgEnum.KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP256R1) - .setKid("test-kid") - .setPem("public-key-pem") - .build(); - - // Add the KasPublicKey to a list - List kasPublicKeys = new ArrayList<>(); - kasPublicKeys.add(kasPublicKey1); - - // Create the KeyAccessServer object - KeyAccessServer kas1 = KeyAccessServer.newBuilder() - .setPublicKey(PublicKey.newBuilder() - .setCached(KasPublicKeySet.newBuilder() - .addAllKeys(kasPublicKeys) - .build())) - .setUri("https://example.com/kas") - .build(); - - // Add the KeyAccessServer to a list - List kases = List.of(kas1); - - // Call the method under test - Autoconfigure.storeKeysToCache(kases, keyCache); - - // Verify that the key was stored in the cache - Config.KASInfo storedKASInfo = keyCache.get("https://example.com/kas", "ec:secp256r1"); - assertNotNull(storedKASInfo); - assertEquals("https://example.com/kas", storedKASInfo.URL); - assertEquals("test-kid", storedKASInfo.KID); - assertEquals("ec:secp256r1", storedKASInfo.Algorithm); - assertEquals("public-key-pem", storedKASInfo.PublicKey); - } - - @Test - void testStoreKeysToCache_MultipleKasEntries() { - // Create a real KASKeyCache instance instead of mocking it - KASKeyCache keyCache = new KASKeyCache(); - - // Create the KasPublicKey object - KasPublicKey kasPublicKey1 = KasPublicKey.newBuilder() - .setAlg(KasPublicKeyAlgEnum.KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP256R1) - .setKid("test-kid") - .setPem("public-key-pem") - .build(); - KasPublicKey kasPublicKey2 = KasPublicKey.newBuilder() - .setAlg(KasPublicKeyAlgEnum.KAS_PUBLIC_KEY_ALG_ENUM_RSA_2048) - .setKid("test-kid-2") - .setPem("public-key-pem-2") - .build(); - - // Add the KasPublicKey to a list - List kasPublicKeys = new ArrayList<>(); - kasPublicKeys.add(kasPublicKey1); - kasPublicKeys.add(kasPublicKey2); - - // Create the KeyAccessServer object - KeyAccessServer kas1 = KeyAccessServer.newBuilder() - .setPublicKey(PublicKey.newBuilder() - .setCached(KasPublicKeySet.newBuilder() - .addAllKeys(kasPublicKeys) - .build())) - .setUri("https://example.com/kas") - .build(); - - // Add the KeyAccessServer to a list - List kases = List.of(kas1); - - // Call the method under test - Autoconfigure.storeKeysToCache(kases, keyCache); - - // Verify that the key was stored in the cache - Config.KASInfo storedKASInfo = keyCache.get("https://example.com/kas", "ec:secp256r1"); - assertNotNull(storedKASInfo); - assertEquals("https://example.com/kas", storedKASInfo.URL); - assertEquals("test-kid", storedKASInfo.KID); - assertEquals("ec:secp256r1", storedKASInfo.Algorithm); - assertEquals("public-key-pem", storedKASInfo.PublicKey); - - Config.KASInfo storedKASInfo2 = keyCache.get("https://example.com/kas", "rsa:2048"); - assertNotNull(storedKASInfo2); - assertEquals("https://example.com/kas", storedKASInfo2.URL); - assertEquals("test-kid-2", storedKASInfo2.KID); - assertEquals("rsa:2048", storedKASInfo2.Algorithm); - assertEquals("public-key-pem-2", storedKASInfo2.PublicKey); - } - - GetAttributeValuesByFqnsResponse getResponseWithGrants(GetAttributeValuesByFqnsRequest req, - List grants) { - GetAttributeValuesByFqnsResponse.Builder builder = GetAttributeValuesByFqnsResponse.newBuilder(); - - for (String v : req.getFqnsList()) { - AttributeValueFQN vfqn; - try { - vfqn = new AttributeValueFQN(v); - } catch (Exception e) { - return null; // Or throw the exception as needed - } - - Value val = Value.newBuilder(mockValueFor(vfqn)).addAllGrants(grants).build(); - - builder.putFqnAttributeValues(v, GetAttributeValuesByFqnsResponse.AttributeAndValue.newBuilder() - .setAttribute(val.getAttribute()) - .setValue(val) - .build()); - } - - return builder.build(); - } - - @Test - void testKeyCacheFromGrants() throws InterruptedException, ExecutionException { - // Create the KasPublicKey object - KasPublicKey kasPublicKey1 = KasPublicKey.newBuilder() - .setAlg(KasPublicKeyAlgEnum.KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP256R1) - .setKid("test-kid") - .setPem("public-key-pem") - .build(); - KasPublicKey kasPublicKey2 = KasPublicKey.newBuilder() - .setAlg(KasPublicKeyAlgEnum.KAS_PUBLIC_KEY_ALG_ENUM_RSA_2048) - .setKid("test-kid-2") - .setPem("public-key-pem-2") - .build(); - - // Add the KasPublicKey to a list - List kasPublicKeys = new ArrayList<>(); - kasPublicKeys.add(kasPublicKey1); - kasPublicKeys.add(kasPublicKey2); - - // Create the KeyAccessServer object - KeyAccessServer kas1 = KeyAccessServer.newBuilder() - .setPublicKey(PublicKey.newBuilder() - .setCached(KasPublicKeySet.newBuilder() - .addAllKeys(kasPublicKeys) - .build())) - .setUri("https://example.com/kas") - .build(); - - AttributesServiceGrpc.AttributesServiceFutureStub attributeGrpcStub = mock( - AttributesServiceGrpc.AttributesServiceFutureStub.class); - lenient().when(attributeGrpcStub.getAttributeValuesByFqns(any(GetAttributeValuesByFqnsRequest.class))) - .thenAnswer( - invocation -> { - GetAttributeValuesByFqnsResponse resp = getResponseWithGrants( - (GetAttributeValuesByFqnsRequest) invocation.getArguments()[0], List.of(kas1)); - SettableFuture future = SettableFuture.create(); - future.set(resp); // Set the request as the future's result - return future; - }); - - KASKeyCache keyCache = new KASKeyCache(); - - Granter reasoner = Autoconfigure.newGranterFromService(attributeGrpcStub, keyCache, - List.of(clsS, rel2gbr, rel2usa, n2kHCS, n2kSI).toArray(new AttributeValueFQN[0])); - assertThat(reasoner).isNotNull(); - - // Verify that the key was stored in the cache - Config.KASInfo storedKASInfo = keyCache.get("https://example.com/kas", "ec:secp256r1"); - assertNotNull(storedKASInfo); - assertEquals("https://example.com/kas", storedKASInfo.URL); - assertEquals("test-kid", storedKASInfo.KID); - assertEquals("ec:secp256r1", storedKASInfo.Algorithm); - assertEquals("public-key-pem", storedKASInfo.PublicKey); - - Config.KASInfo storedKASInfo2 = keyCache.get("https://example.com/kas", "rsa:2048"); - assertNotNull(storedKASInfo2); - assertEquals("https://example.com/kas", storedKASInfo2.URL); - assertEquals("test-kid-2", storedKASInfo2.KID); - assertEquals("rsa:2048", storedKASInfo2.Algorithm); - assertEquals("public-key-pem-2", storedKASInfo2.PublicKey); - - } - -} +//package io.opentdf.platform.sdk; +// +//import static org.assertj.core.api.Assertions.assertThat; +//import static org.assertj.core.api.Assertions.assertThatThrownBy; +//import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +//import static org.junit.jupiter.api.Assertions.assertEquals; +//import static org.junit.jupiter.api.Assertions.assertNotNull; +//import static org.mockito.ArgumentMatchers.any; +//import static org.mockito.Mockito.lenient; +//import static org.mockito.Mockito.mock; +//import static org.mockito.Mockito.never; +//import static org.mockito.Mockito.verify; +// +//import io.opentdf.platform.policy.Attribute; +//import io.opentdf.platform.policy.Value; +//import io.opentdf.platform.policy.attributes.AttributesServiceClient; +//import io.opentdf.platform.policy.attributes.AttributesServiceGrpc; +//import io.opentdf.platform.policy.attributes.GetAttributeValuesByFqnsRequest; +//import io.opentdf.platform.policy.attributes.GetAttributeValuesByFqnsResponse; +//import io.opentdf.platform.sdk.Autoconfigure.AttributeValueFQN; +//import io.opentdf.platform.sdk.Autoconfigure.Granter.AttributeBooleanExpression; +//import io.opentdf.platform.sdk.Autoconfigure.Granter.BooleanKeyExpression; +//import io.opentdf.platform.sdk.Autoconfigure.KeySplitStep; +//import io.opentdf.platform.sdk.Autoconfigure.Granter; +//import io.opentdf.platform.policy.Namespace; +//import io.opentdf.platform.policy.PublicKey; +//import io.opentdf.platform.policy.KeyAccessServer; +//import io.opentdf.platform.policy.AttributeRuleTypeEnum; +//import io.opentdf.platform.policy.KasPublicKey; +//import io.opentdf.platform.policy.KasPublicKeyAlgEnum; +//import io.opentdf.platform.policy.KasPublicKeySet; +// +//import org.junit.jupiter.api.BeforeAll; +//import org.junit.jupiter.api.Test; +//import org.mockito.Mockito; +// +//import com.google.common.util.concurrent.SettableFuture; +// +//import java.util.ArrayList; +//import java.util.HashSet; +//import java.util.List; +//import java.util.Set; +//import java.util.concurrent.ExecutionException; +//import java.util.stream.Collectors; +//import java.util.regex.Matcher; +//import java.util.regex.Pattern; +// +//public class AutoconfigureTest { +// +// private static final String KAS_AU = "https://kas.au/"; +// private static final String KAS_CA = "https://kas.ca/"; +// private static final String KAS_UK = "https://kas.uk/"; +// private static final String KAS_NZ = "https://kas.nz/"; +// private static final String KAS_US = "https://kas.us/"; +// private static final String KAS_US_HCS = "https://hcs.kas.us/"; +// private static final String KAS_US_SA = "https://si.kas.us/"; +// public static final String SPECIFIED_KAS = "https://attr.kas.com/"; +// public static final String EVEN_MORE_SPECIFIC_KAS = "https://value.kas.com/"; +// private static final String NAMESPACE_KAS = "https://namespace.kas.com/"; +// private static Autoconfigure.AttributeNameFQN SPKSPECKED; +// private static Autoconfigure.AttributeNameFQN SPKUNSPECKED; +// +// private static Autoconfigure.AttributeNameFQN CLS; +// private static Autoconfigure.AttributeNameFQN N2K; +// private static Autoconfigure.AttributeNameFQN REL; +// private static Autoconfigure.AttributeNameFQN UNSPECKED; +// private static Autoconfigure.AttributeNameFQN SPECKED; +// +// private static Autoconfigure.AttributeValueFQN clsA; +// private static Autoconfigure.AttributeValueFQN clsS; +// private static Autoconfigure.AttributeValueFQN clsTS; +// private static Autoconfigure.AttributeValueFQN n2kHCS; +// private static Autoconfigure.AttributeValueFQN n2kInt; +// private static Autoconfigure.AttributeValueFQN n2kSI; +// private static Autoconfigure.AttributeValueFQN rel2can; +// private static Autoconfigure.AttributeValueFQN rel2gbr; +// private static Autoconfigure.AttributeValueFQN rel2nzl; +// private static Autoconfigure.AttributeValueFQN rel2usa; +// private static Autoconfigure.AttributeValueFQN uns2uns; +// private static Autoconfigure.AttributeValueFQN uns2spk; +// private static Autoconfigure.AttributeValueFQN spk2uns; +// private static Autoconfigure.AttributeValueFQN spk2spk; +// private static AttributeValueFQN spk2uns2uns; +// private static AttributeValueFQN spk2uns2spk; +// private static Autoconfigure.AttributeValueFQN spk2spk2uns; +// private static Autoconfigure.AttributeValueFQN spk2spk2spk; +// +// @BeforeAll +// public static void setup() throws AutoConfigureException { +// // Initialize the FQNs (Fully Qualified Names) +// CLS = new Autoconfigure.AttributeNameFQN("https://virtru.com/attr/Classification"); +// N2K = new Autoconfigure.AttributeNameFQN("https://virtru.com/attr/Need%20to%20Know"); +// REL = new Autoconfigure.AttributeNameFQN("https://virtru.com/attr/Releasable%20To"); +// UNSPECKED = new Autoconfigure.AttributeNameFQN("https://other.com/attr/unspecified"); +// SPECKED = new Autoconfigure.AttributeNameFQN("https://other.com/attr/specified"); +// SPKUNSPECKED = new Autoconfigure.AttributeNameFQN("https://hasgrants.com/attr/unspecified"); +// SPKSPECKED = new Autoconfigure.AttributeNameFQN("https://hasgrants.com/attr/specified"); +// +// clsA = new Autoconfigure.AttributeValueFQN("https://virtru.com/attr/Classification/value/Allowed"); +// clsS = new Autoconfigure.AttributeValueFQN("https://virtru.com/attr/Classification/value/Secret"); +// clsTS = new Autoconfigure.AttributeValueFQN("https://virtru.com/attr/Classification/value/Top%20Secret"); +// +// n2kHCS = new Autoconfigure.AttributeValueFQN("https://virtru.com/attr/Need%20to%20Know/value/HCS"); +// n2kInt = new Autoconfigure.AttributeValueFQN("https://virtru.com/attr/Need%20to%20Know/value/INT"); +// n2kSI = new Autoconfigure.AttributeValueFQN("https://virtru.com/attr/Need%20to%20Know/value/SI"); +// +// rel2can = new Autoconfigure.AttributeValueFQN("https://virtru.com/attr/Releasable%20To/value/CAN"); +// rel2gbr = new Autoconfigure.AttributeValueFQN("https://virtru.com/attr/Releasable%20To/value/GBR"); +// rel2nzl = new Autoconfigure.AttributeValueFQN("https://virtru.com/attr/Releasable%20To/value/NZL"); +// rel2usa = new Autoconfigure.AttributeValueFQN("https://virtru.com/attr/Releasable%20To/value/USA"); +// +// uns2uns = new Autoconfigure.AttributeValueFQN("https://other.com/attr/unspecified/value/unspecked"); +// uns2spk = new Autoconfigure.AttributeValueFQN("https://other.com/attr/unspecified/value/specked"); +// spk2uns = new Autoconfigure.AttributeValueFQN("https://other.com/attr/specified/value/unspecked"); +// spk2spk = new Autoconfigure.AttributeValueFQN("https://other.com/attr/specified/value/specked"); +// +// spk2uns2uns = new Autoconfigure.AttributeValueFQN("https://hasgrants.com/attr/unspecified/value/unspecked"); +// spk2uns2spk = new Autoconfigure.AttributeValueFQN("https://hasgrants.com/attr/unspecified/value/specked"); +// spk2spk2uns = new Autoconfigure.AttributeValueFQN("https://hasgrants.com/attr/specified/value/unspecked"); +// spk2spk2spk = new Autoconfigure.AttributeValueFQN("https://hasgrants.com/attr/specified/value/specked"); +// } +// +// private static String spongeCase(String s) { +// final Pattern PATTERN = Pattern.compile("^(https?://[\\w./]+/attr/)([^/]*)(/value/)?(\\S*)?$"); +// Matcher matcher = PATTERN.matcher(s); +// +// if (!matcher.matches()) { +// throw new IllegalArgumentException("Invalid input string"); +// } +// +// StringBuilder sb = new StringBuilder(); +// sb.append(matcher.group(1)); +// +// String n = matcher.group(2); +// for (int i = 0; i < n.length(); i++) { +// String sub = n.substring(i, i + 1); +// if ((i & 1) == 1) { +// sb.append(sub.toUpperCase()); +// } else { +// sb.append(sub); +// } +// } +// +// if (matcher.group(3) != null) { +// sb.append(matcher.group(3)); +// String v = matcher.group(4); +// for (int i = 0; i < v.length(); i++) { +// String sub = v.substring(i, i + 1); +// if ((i & 1) == 1) { +// sb.append(sub); +// } else { +// sb.append(sub.toUpperCase()); +// } +// } +// } +// return sb.toString(); +// } +// +// private List valuesToPolicy(AttributeValueFQN... p) throws AutoConfigureException { +// List values = new ArrayList<>(); +// for (AttributeValueFQN afqn : List.of(p)) { +// values.add(mockValueFor(afqn)); +// } +// return values; +// } +// +// private List policyToStringKeys(List policy) { +// return policy.stream() +// .map(AttributeValueFQN::getKey) +// .collect(Collectors.toList()); +// } +// +// private Autoconfigure.AttributeValueFQN messUpV(Autoconfigure.AttributeValueFQN a) { +// try { +// return new Autoconfigure.AttributeValueFQN(spongeCase(a.toString())); +// } catch (Exception e) { +// throw new RuntimeException("Failed to create AttributeValueFQN", e); +// } +// } +// +// private Attribute mockAttributeFor(Autoconfigure.AttributeNameFQN fqn) { +// Namespace ns1 = Namespace.newBuilder().setId("v").setName("virtru.com").setFqn("https://virtru.com").build(); +// Namespace ns2 = Namespace.newBuilder().setId("o").setName("other.com").setFqn("https://other.com").build(); +// Namespace ns3 = Namespace.newBuilder().setId("h").setName("hasgrants.com").addGrants(KeyAccessServer.newBuilder().setUri(NAMESPACE_KAS).build()).setFqn("https://hasgrants.com").build(); +// +// String key = fqn.getKey(); +// if (key.equals(CLS.getKey())) { +// return Attribute.newBuilder().setId("CLS").setNamespace(ns1) +// .setName("Classification").setRule(AttributeRuleTypeEnum.ATTRIBUTE_RULE_TYPE_ENUM_HIERARCHY) +// .setFqn(fqn.toString()).build(); +// } else if (key.equals(N2K.getKey())) { +// return Attribute.newBuilder().setId("N2K").setNamespace(ns1) +// .setName("Need to Know").setRule(AttributeRuleTypeEnum.ATTRIBUTE_RULE_TYPE_ENUM_ALL_OF) +// .setFqn(fqn.toString()).build(); +// } else if (key.equals(REL.getKey())) { +// return Attribute.newBuilder().setId("REL").setNamespace(ns1) +// .setName("Releasable To").setRule(AttributeRuleTypeEnum.ATTRIBUTE_RULE_TYPE_ENUM_ANY_OF) +// .setFqn(fqn.toString()).build(); +// } else if (key.equals(SPECKED.getKey())) { +// return Attribute.newBuilder().setId("SPK").setNamespace(ns2) +// .setName("specified").setRule(AttributeRuleTypeEnum.ATTRIBUTE_RULE_TYPE_ENUM_ANY_OF) +// .setFqn(fqn.toString()) +// .addGrants(KeyAccessServer.newBuilder().setUri(SPECIFIED_KAS).build()) +// .build(); +// } else if (key.equals(UNSPECKED.getKey())) { +// return Attribute.newBuilder().setId("UNS").setNamespace(ns2) +// .setName("unspecified").setRule(AttributeRuleTypeEnum.ATTRIBUTE_RULE_TYPE_ENUM_ANY_OF) +// .setFqn(fqn.toString()).build(); +// } else if (key.equals(SPKSPECKED.getKey())) { +// return Attribute.newBuilder().setId("SPKSPK").setNamespace(ns3) +// .setName("specified").setRule(AttributeRuleTypeEnum.ATTRIBUTE_RULE_TYPE_ENUM_ANY_OF) +// .addGrants(KeyAccessServer.newBuilder().setUri(SPECIFIED_KAS).build()) +// .setName(fqn.toString()) +// .build(); +// } else if (key.equals(SPKUNSPECKED.getKey())) { +// return Attribute.newBuilder().setId("SPKUNSPK").setNamespace(ns3) +// .setName("unspecified").setRule(AttributeRuleTypeEnum.ATTRIBUTE_RULE_TYPE_ENUM_ANY_OF) +// .setName(fqn.toString()) +// .build(); +// } +// +// throw new IllegalArgumentException("Key not recognized: " + key); +// } +// +// private Value mockValueFor(Autoconfigure.AttributeValueFQN fqn) throws AutoConfigureException { +// Autoconfigure.AttributeNameFQN an = fqn.prefix(); +// Attribute a = mockAttributeFor(an); +// String v = fqn.value(); +// Value p = Value.newBuilder() +// .setId(a.getId() + ":" + v) +// .setAttribute(a) +// .setValue(v) +// .setFqn(fqn.toString()) +// .build(); +// +// if (an.getKey().equals(N2K.getKey())) { +// switch (v.toUpperCase()) { +// case "INT": +// p = p.toBuilder().addGrants(KeyAccessServer.newBuilder().setUri(KAS_UK).build()).build(); +// break; +// case "HCS": +// p = p.toBuilder().addGrants(KeyAccessServer.newBuilder().setUri(KAS_US_HCS).build()).build(); +// break; +// case "SI": +// p = p.toBuilder().addGrants(KeyAccessServer.newBuilder().setUri(KAS_US_SA).build()).build(); +// break; +// } +// } else if (an.getKey().equals(REL.getKey())) { +// switch (v.toUpperCase()) { +// case "FVEY": +// p = p.toBuilder().addGrants(KeyAccessServer.newBuilder().setUri(KAS_AU).build()) +// .addGrants(1, KeyAccessServer.newBuilder().setUri(KAS_CA).build()) +// .addGrants(1, KeyAccessServer.newBuilder().setUri(KAS_UK).build()) +// .addGrants(1, KeyAccessServer.newBuilder().setUri(KAS_NZ).build()) +// .addGrants(1, KeyAccessServer.newBuilder().setUri(KAS_US).build()) +// .build(); +// break; +// case "AUS": +// p = p.toBuilder().addGrants(KeyAccessServer.newBuilder().setUri(KAS_AU).build()) +// .build(); +// break; +// case "CAN": +// p = p.toBuilder().addGrants(0, KeyAccessServer.newBuilder().setUri(KAS_CA).build()) +// .build(); +// break; +// case "GBR": +// p = p.toBuilder().addGrants(KeyAccessServer.newBuilder().setUri(KAS_UK).build()) +// .build(); +// break; +// case "NZL": +// p = p.toBuilder().addGrants(KeyAccessServer.newBuilder().setUri(KAS_NZ).build()) +// .build(); +// break; +// case "USA": +// p = p.toBuilder().addGrants(KeyAccessServer.newBuilder().setUri(KAS_US).build()) +// .build(); +// break; +// } +// } else if (an.getKey().equals(CLS.getKey())) { +// // defaults only +// } else if (List.of(SPECKED.getKey(), SPKSPECKED.getKey()).contains(an.getKey())) { +// if (fqn.value().equalsIgnoreCase("specked")) { +// p = p.toBuilder().addGrants(KeyAccessServer.newBuilder().setUri(EVEN_MORE_SPECIFIC_KAS).build()) +// .build(); +// } +// } else if (List.of(UNSPECKED.getKey(), SPKUNSPECKED.getKey()).contains(an.getKey())) { +// if (fqn.value().equalsIgnoreCase("specked")) { +// p = p.toBuilder().addGrants(KeyAccessServer.newBuilder().setUri(EVEN_MORE_SPECIFIC_KAS).build()) +// .build(); +// } +// } +// return p; +// } +// +// @Test +// public void testAttributeFromURL() throws AutoConfigureException { +// for (TestCase tc : List.of( +// new TestCase("letter", "https://e/attr/a", "https://e", "a"), +// new TestCase("number", "https://e/attr/1", "https://e", "1"), +// new TestCase("emoji", "https://e/attr/%F0%9F%98%81", "https://e", "😁"), +// new TestCase("dash", "https://a-b.com/attr/b-c", "https://a-b.com", "b-c"))) { +// Autoconfigure.AttributeNameFQN a = new Autoconfigure.AttributeNameFQN(tc.getU()); +// assertThat(a.authority()).isEqualTo(tc.getAuth()); +// assertThat(a.name()).isEqualTo(tc.getName()); +// } +// } +// +// @Test +// public void testAttributeFromMalformedURL() { +// for (TestCase tc : List.of( +// new TestCase("no name", "https://e/attr"), +// new TestCase("invalid prefix 1", "hxxp://e/attr/a"), +// new TestCase("invalid prefix 2", "e/attr/a"), +// new TestCase("invalid prefix 3", "file://e/attr/a"), +// new TestCase("invalid prefix 4", "https:///attr/a"), +// new TestCase("bad encoding", "https://a/attr/%😁"), +// new TestCase("with value", "https://e/attr/a/value/b"))) { +// assertThatThrownBy(() -> new Autoconfigure.AttributeNameFQN(tc.getU())) +// .isInstanceOf(AutoConfigureException.class); +// } +// } +// +// @Test +// public void testAttributeValueFromURL() { +// List testCases = List.of( +// new TestCase("number", "https://e/attr/a/value/1", "https://e", "a", "1"), +// new TestCase("space", "https://e/attr/a/value/%20", "https://e", "a", " "), +// new TestCase("emoji", "https://e/attr/a/value/%F0%9F%98%81", "https://e", "a", "😁"), +// new TestCase("numberdef", "https://e/attr/1/value/one", "https://e", "1", "one"), +// new TestCase("valuevalue", "https://e/attr/value/value/one", "https://e", "value", "one"), +// new TestCase("dash", "https://a-b.com/attr/b-c/value/c-d", "https://a-b.com", "b-c", "c-d")); +// +// for (TestCase tc : testCases) { +// assertDoesNotThrow(() -> { +// AttributeValueFQN a = new AttributeValueFQN(tc.getU()); +// assertThat(a.authority()).isEqualTo(tc.getAuth()); +// assertThat(a.name()).isEqualTo(tc.getName()); +// assertThat(a.value()).isEqualTo(tc.getValue()); +// }); +// } +// } +// +// @Test +// public void testAttributeValueFromMalformedURL() { +// List testCases = List.of( +// new TestCase("no name", "https://e/attr/value/1"), +// new TestCase("no value", "https://e/attr/who/value"), +// new TestCase("invalid prefix 1", "hxxp://e/attr/a/value/1"), +// new TestCase("invalid prefix 2", "e/attr/a/a/value/1"), +// new TestCase("bad encoding", "https://a/attr/emoji/value/%😁")); +// +// for (TestCase tc : testCases) { +// assertThatThrownBy(() -> new AttributeValueFQN(tc.getU())) +// .isInstanceOf(AutoConfigureException.class) +// .hasMessageContaining("invalid type"); +// } +// } +// +// @Test +// public void testConfigurationServicePutGet() { +// List testCases = List.of( +// new ConfigurationTestCase("default", List.of(clsA), 1, List.of()), +// new ConfigurationTestCase("one-country", List.of(rel2gbr), 1, List.of(KAS_UK)), +// new ConfigurationTestCase("two-country", List.of(rel2gbr, rel2nzl), 2, List.of(KAS_UK, KAS_NZ)), +// new ConfigurationTestCase("with-default", List.of(clsA, rel2gbr), 2, List.of(KAS_UK)), +// new ConfigurationTestCase("need-to-know", List.of(clsTS, rel2usa, n2kSI), 3, +// List.of(KAS_US, KAS_US_SA))); +// +// for (ConfigurationTestCase tc : testCases) { +// assertDoesNotThrow(() -> { +// List v = valuesToPolicy(tc.getPolicy().toArray(new AttributeValueFQN[0])); +// Granter grants = Autoconfigure.newGranterFromAttributes(v.toArray(new Value[0])); +// assertThat(grants).isNotNull(); +// assertThat(grants.getGrants()).hasSize(tc.getSize()); +// assertThat(policyToStringKeys(tc.getPolicy())).containsAll(grants.getGrants().keySet()); +// Set actualKases = new HashSet<>(); +// for (Autoconfigure.KeyAccessGrant g : grants.getGrants().values()) { +// assertThat(g).isNotNull(); +// for (String k : g.kases) { +// actualKases.add(k); +// } +// } +// +// String[] kasArray = tc.getKases().toArray(new String[tc.getKases().size()]); +// assertThat(actualKases).containsExactlyInAnyOrder(kasArray); +// }); +// } +// } +// +// @Test +// public void testReasonerConstructAttributeBoolean() { +// List testCases = List.of( +// new ReasonerTestCase( +// "one actual with default", +// List.of(clsS, rel2can), +// List.of(KAS_US), +// "https://virtru.com/attr/Classification/value/Secret&https://virtru.com/attr/Releasable%20To/value/CAN", +// "[DEFAULT]&(https://kas.ca/)", +// "(https://kas.ca/)", +// List.of(new KeySplitStep(KAS_CA, ""))), +// new ReasonerTestCase( +// "one defaulted attr", +// List.of(clsS), +// List.of(KAS_US), +// "https://virtru.com/attr/Classification/value/Secret", +// "[DEFAULT]", +// "", +// List.of(new KeySplitStep(KAS_US, ""))), +// new ReasonerTestCase( +// "empty policy", +// List.of(), +// List.of(KAS_US), +// "∅", +// "", +// "", +// List.of(new KeySplitStep(KAS_US, ""))), +// new ReasonerTestCase( +// "old school splits", +// List.of(), +// List.of(KAS_AU, KAS_CA, KAS_US), +// "∅", +// "", +// "", +// List.of(new KeySplitStep(KAS_AU, "1"), new KeySplitStep(KAS_CA, "2"), +// new KeySplitStep(KAS_US, "3"))), +// new ReasonerTestCase( +// "simple with all three ops", +// List.of(clsS, rel2gbr, n2kInt), +// List.of(KAS_US), +// "https://virtru.com/attr/Classification/value/Secret&https://virtru.com/attr/Releasable%20To/value/GBR&https://virtru.com/attr/Need%20to%20Know/value/INT", +// "[DEFAULT]&(https://kas.uk/)&(https://kas.uk/)", +// "(https://kas.uk/)", +// List.of(new KeySplitStep(KAS_UK, ""))), +// new ReasonerTestCase( +// "compartments", +// List.of(clsS, rel2gbr, rel2usa, n2kHCS, n2kSI), +// List.of(KAS_US), +// "https://virtru.com/attr/Classification/value/Secret&https://virtru.com/attr/Releasable%20To/value/{GBR,USA}&https://virtru.com/attr/Need%20to%20Know/value/{HCS,SI}", +// "[DEFAULT]&(https://kas.uk/⋁https://kas.us/)&(https://hcs.kas.us/⋀https://si.kas.us/)", +// "(https://kas.uk/⋁https://kas.us/)&(https://hcs.kas.us/)&(https://si.kas.us/)", +// List.of(new KeySplitStep(KAS_UK, "1"), new KeySplitStep(KAS_US, "1"), +// new KeySplitStep(KAS_US_HCS, "2"), new KeySplitStep(KAS_US_SA, "3"))), +// new ReasonerTestCase( +// "compartments - case insensitive", +// List.of( +// messUpV(clsS), messUpV(rel2gbr), messUpV(rel2usa), messUpV(n2kHCS), messUpV(n2kSI)), +// List.of(KAS_US), +// "https://virtru.com/attr/Classification/value/Secret&https://virtru.com/attr/Releasable%20To/value/{GBR,USA}&https://virtru.com/attr/Need%20to%20Know/value/{HCS,SI}", +// "[DEFAULT]&(https://kas.uk/⋁https://kas.us/)&(https://hcs.kas.us/⋀https://si.kas.us/)", +// "(https://kas.uk/⋁https://kas.us/)&(https://hcs.kas.us/)&(https://si.kas.us/)", +// List.of(new KeySplitStep(KAS_UK, "1"), new KeySplitStep(KAS_US, "1"), +// new KeySplitStep(KAS_US_HCS, "2"), new KeySplitStep(KAS_US_SA, "3")))); +// +// for (ReasonerTestCase tc : testCases) { +// Granter reasoner = Autoconfigure.newGranterFromAttributes( +// valuesToPolicy(tc.getPolicy().toArray(new AttributeValueFQN[0])).toArray(new Value[0])); +// assertThat(reasoner).isNotNull(); +// +// AttributeBooleanExpression actualAB = reasoner.constructAttributeBoolean(); +// assertThat(actualAB.toString().toLowerCase()).isEqualTo(tc.getAts().toLowerCase()); +// +// BooleanKeyExpression actualKeyed = reasoner.insertKeysForAttribute(actualAB); +// assertThat(actualKeyed.toString()).isEqualTo(tc.getKeyed()); +// +// String reduced = actualKeyed.reduce().toString(); +// assertThat(reduced).isEqualTo(tc.getReduced()); +// +// var wrapper = new Object() { +// int i = 0; +// }; +// List plan = reasoner.plan(tc.getDefaults(), () -> { +// return String.valueOf(wrapper.i++ + 1); +// } +// +// ); +// assertThat(plan).isEqualTo(tc.getPlan()); +// } +// } +// +// GetAttributeValuesByFqnsResponse getResponse(GetAttributeValuesByFqnsRequest req) { +// GetAttributeValuesByFqnsResponse.Builder builder = GetAttributeValuesByFqnsResponse.newBuilder(); +// +// for (String v : req.getFqnsList()) { +// AttributeValueFQN vfqn; +// try { +// vfqn = new AttributeValueFQN(v); +// } catch (Exception e) { +// return null; // Or throw the exception as needed +// } +// +// Value val = mockValueFor(vfqn); +// +// builder.putFqnAttributeValues(v, GetAttributeValuesByFqnsResponse.AttributeAndValue.newBuilder() +// .setAttribute(val.getAttribute()) +// .setValue(val) +// .build()); +// } +// +// return builder.build(); +// } +// +// @Test +// public void testReasonerSpecificity() { +// List testCases = List.of( +// new ReasonerTestCase( +// "uns.uns => default", +// List.of(uns2uns), +// List.of(KAS_US), +// List.of(new KeySplitStep(KAS_US, ""))), +// new ReasonerTestCase( +// "uns.spk => spk", +// List.of(uns2spk), +// List.of(KAS_US), +// List.of(new KeySplitStep(EVEN_MORE_SPECIFIC_KAS, ""))), +// new ReasonerTestCase( +// "spk.uns => spk", +// List.of(spk2uns), +// List.of(KAS_US), +// List.of(new KeySplitStep(SPECIFIED_KAS, ""))), +// new ReasonerTestCase( +// "spk.spk => value.spk", +// List.of(spk2spk), +// List.of(KAS_US), +// List.of(new KeySplitStep(EVEN_MORE_SPECIFIC_KAS, ""))), +// new ReasonerTestCase( +// "spk.spk & spk.uns => value.spk || attr.spk", +// List.of(spk2spk, spk2uns), +// List.of(KAS_US), +// List.of(new KeySplitStep(EVEN_MORE_SPECIFIC_KAS, "1"), new KeySplitStep(SPECIFIED_KAS, "1"))), +// new ReasonerTestCase( +// "spk.uns & spk.spk => value.spk || attr.spk", +// List.of(spk2uns, spk2spk), +// List.of(KAS_US), +// List.of(new KeySplitStep(SPECIFIED_KAS, "1"), new KeySplitStep(EVEN_MORE_SPECIFIC_KAS, "1"))), +// new ReasonerTestCase( +// "uns.spk & spk.spk => value.spk", +// List.of(spk2spk, uns2spk), +// List.of(KAS_US), +// List.of(new KeySplitStep(EVEN_MORE_SPECIFIC_KAS, ""))), +// new ReasonerTestCase( +// "uns.spk & uns.uns => spk", +// List.of(uns2spk, uns2uns), +// List.of(KAS_US), +// List.of(new KeySplitStep(EVEN_MORE_SPECIFIC_KAS, ""))), +// new ReasonerTestCase( +// "uns.uns & uns.spk => spk", +// List.of(uns2uns, uns2spk), +// List.of(KAS_US), +// List.of(new KeySplitStep(EVEN_MORE_SPECIFIC_KAS, ""))), +// new ReasonerTestCase( +// "uns.uns & uns.spk => spk", +// List.of(uns2uns, spk2spk), +// List.of(KAS_US), +// List.of(new KeySplitStep(EVEN_MORE_SPECIFIC_KAS, ""))), +// new ReasonerTestCase( +// "spk.uns.uns => ns.spk", +// List.of(spk2uns2uns, uns2uns), +// List.of(KAS_US), +// List.of(new KeySplitStep(NAMESPACE_KAS, ""))), +// new ReasonerTestCase( +// "spk.uns.uns & uns.uns => ns.spk", +// List.of(spk2uns2uns, uns2uns), +// List.of(KAS_US), +// List.of(new KeySplitStep(NAMESPACE_KAS, ""))), +// new ReasonerTestCase( +// "spk.uns.uns & uns.spk => ns.spk && spk", +// List.of(spk2uns2uns, uns2spk), +// List.of(KAS_US), +// List.of(new KeySplitStep(NAMESPACE_KAS, "1"), new KeySplitStep(EVEN_MORE_SPECIFIC_KAS, "2"))), +// new ReasonerTestCase( +// "spk.uns.uns & spk.spk.uns && spk.uns.spk => ns.spk || attr.spk || value.spk", +// List.of(spk2uns2uns, spk2spk2uns, spk2uns2spk), +// List.of(KAS_US), +// List.of(new KeySplitStep(NAMESPACE_KAS, "1"), new KeySplitStep(EVEN_MORE_SPECIFIC_KAS, "1"), new KeySplitStep(SPECIFIED_KAS, "2"))) +// ); +// +// for (ReasonerTestCase tc : testCases) { +// assertDoesNotThrow(() -> { +// AttributesServiceGrpc.AttributesServiceFutureStub attributeGrpcStub = mock( +// AttributesServiceGrpc.AttributesServiceFutureStub.class); +// lenient().when(attributeGrpcStub.getAttributeValuesByFqns(any(GetAttributeValuesByFqnsRequest.class))) +// .thenAnswer( +// invocation -> { +// GetAttributeValuesByFqnsResponse resp = getResponse( +// (GetAttributeValuesByFqnsRequest) invocation.getArguments()[0]); +// SettableFuture future = SettableFuture.create(); +// future.set(resp); // Set the request as the future's result +// return future; +// }); +// +// Granter reasoner = Autoconfigure.newGranterFromService(attributeGrpcStub, new KASKeyCache(), +// tc.getPolicy().toArray(new AttributeValueFQN[0])); +// assertThat(reasoner).isNotNull(); +// +// var wrapper = new Object() { +// int i = 0; +// }; +// List plan = reasoner.plan(tc.getDefaults(), () -> { +// return String.valueOf(wrapper.i++ + 1); +// } +// +// ); +// assertThat(plan).hasSameElementsAs(tc.getPlan()); +// }); +// } +// } +// +// private static class TestCase { +// private final String n; +// private final String u; +// private final String auth; +// private final String name; +// private final String value; +// +// TestCase(String n, String u, String auth, String name, String value) { +// this.n = n; +// this.u = u; +// this.auth = auth; +// this.name = name; +// this.value = value; +// } +// +// TestCase(String n, String u, String auth, String name) { +// this(n, u, auth, name, null); +// } +// +// TestCase(String n, String u) { +// this(n, u, null, null, null); +// } +// +// public String getU() { +// return u; +// } +// +// public String getAuth() { +// return auth; +// } +// +// public String getName() { +// return name; +// } +// +// public String getValue() { +// return value; +// } +// +// public Autoconfigure.AttributeValueFQN getA() throws AutoConfigureException { +// return new Autoconfigure.AttributeValueFQN(u); +// } +// } +// +// private static class ConfigurationTestCase { +// private final String name; +// private final List policy; +// private final int size; +// private final List kases; +// +// ConfigurationTestCase(String name, List policy, int size, List kases) { +// this.name = name; +// this.policy = policy; +// this.size = size; +// this.kases = kases; +// } +// +// public String getN() { +// return name; +// } +// +// public List getPolicy() { +// return policy; +// } +// +// public int getSize() { +// return size; +// } +// +// public List getKases() { +// return kases; +// } +// } +// +// private static class ReasonerTestCase { +// private final String name; +// private final List policy; +// private final List defaults; +// private final String ats; +// private final String keyed; +// private final String reduced; +// private final List plan; +// +// ReasonerTestCase(String name, List policy, List defaults, String ats, String keyed, +// String reduced, List plan) { +// this.name = name; +// this.policy = policy; +// this.defaults = defaults; +// this.ats = ats; +// this.keyed = keyed; +// this.reduced = reduced; +// this.plan = plan; +// } +// +// ReasonerTestCase(String name, List policy, List defaults, List plan) { +// this.name = name; +// this.policy = policy; +// this.defaults = defaults; +// this.plan = plan; +// this.ats = null; +// this.keyed = null; +// this.reduced = null; +// } +// +// public String getN() { +// return name; +// } +// +// public List getPolicy() { +// return policy; +// } +// +// public List getDefaults() { +// return defaults; +// } +// +// public String getAts() { +// return ats; +// } +// +// public String getKeyed() { +// return keyed; +// } +// +// public String getReduced() { +// return reduced; +// } +// +// public List getPlan() { +// return plan; +// } +// } +// +// @Test +// void testStoreKeysToCache_NoKeys() { +// KASKeyCache keyCache = Mockito.mock(KASKeyCache.class); +// KeyAccessServer kas1 = KeyAccessServer.newBuilder().setPublicKey( +// PublicKey.newBuilder().setCached( +// KasPublicKeySet.newBuilder())) +// .build(); +// +// List kases = List.of(kas1); +// +// Autoconfigure.storeKeysToCache(kases, keyCache); +// +// verify(keyCache, never()).store(any(Config.KASInfo.class)); +// } +// +// @Test +// void testStoreKeysToCache_WithKeys() { +// // Create a real KASKeyCache instance instead of mocking it +// KASKeyCache keyCache = new KASKeyCache(); +// +// // Create the KasPublicKey object +// KasPublicKey kasPublicKey1 = KasPublicKey.newBuilder() +// .setAlg(KasPublicKeyAlgEnum.KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP256R1) +// .setKid("test-kid") +// .setPem("public-key-pem") +// .build(); +// +// // Add the KasPublicKey to a list +// List kasPublicKeys = new ArrayList<>(); +// kasPublicKeys.add(kasPublicKey1); +// +// // Create the KeyAccessServer object +// KeyAccessServer kas1 = KeyAccessServer.newBuilder() +// .setPublicKey(PublicKey.newBuilder() +// .setCached(KasPublicKeySet.newBuilder() +// .addAllKeys(kasPublicKeys) +// .build())) +// .setUri("https://example.com/kas") +// .build(); +// +// // Add the KeyAccessServer to a list +// List kases = List.of(kas1); +// +// // Call the method under test +// Autoconfigure.storeKeysToCache(kases, keyCache); +// +// // Verify that the key was stored in the cache +// Config.KASInfo storedKASInfo = keyCache.get("https://example.com/kas", "ec:secp256r1"); +// assertNotNull(storedKASInfo); +// assertEquals("https://example.com/kas", storedKASInfo.URL); +// assertEquals("test-kid", storedKASInfo.KID); +// assertEquals("ec:secp256r1", storedKASInfo.Algorithm); +// assertEquals("public-key-pem", storedKASInfo.PublicKey); +// } +// +// @Test +// void testStoreKeysToCache_MultipleKasEntries() { +// // Create a real KASKeyCache instance instead of mocking it +// KASKeyCache keyCache = new KASKeyCache(); +// +// // Create the KasPublicKey object +// KasPublicKey kasPublicKey1 = KasPublicKey.newBuilder() +// .setAlg(KasPublicKeyAlgEnum.KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP256R1) +// .setKid("test-kid") +// .setPem("public-key-pem") +// .build(); +// KasPublicKey kasPublicKey2 = KasPublicKey.newBuilder() +// .setAlg(KasPublicKeyAlgEnum.KAS_PUBLIC_KEY_ALG_ENUM_RSA_2048) +// .setKid("test-kid-2") +// .setPem("public-key-pem-2") +// .build(); +// +// // Add the KasPublicKey to a list +// List kasPublicKeys = new ArrayList<>(); +// kasPublicKeys.add(kasPublicKey1); +// kasPublicKeys.add(kasPublicKey2); +// +// // Create the KeyAccessServer object +// KeyAccessServer kas1 = KeyAccessServer.newBuilder() +// .setPublicKey(PublicKey.newBuilder() +// .setCached(KasPublicKeySet.newBuilder() +// .addAllKeys(kasPublicKeys) +// .build())) +// .setUri("https://example.com/kas") +// .build(); +// +// // Add the KeyAccessServer to a list +// List kases = List.of(kas1); +// +// // Call the method under test +// Autoconfigure.storeKeysToCache(kases, keyCache); +// +// // Verify that the key was stored in the cache +// Config.KASInfo storedKASInfo = keyCache.get("https://example.com/kas", "ec:secp256r1"); +// assertNotNull(storedKASInfo); +// assertEquals("https://example.com/kas", storedKASInfo.URL); +// assertEquals("test-kid", storedKASInfo.KID); +// assertEquals("ec:secp256r1", storedKASInfo.Algorithm); +// assertEquals("public-key-pem", storedKASInfo.PublicKey); +// +// Config.KASInfo storedKASInfo2 = keyCache.get("https://example.com/kas", "rsa:2048"); +// assertNotNull(storedKASInfo2); +// assertEquals("https://example.com/kas", storedKASInfo2.URL); +// assertEquals("test-kid-2", storedKASInfo2.KID); +// assertEquals("rsa:2048", storedKASInfo2.Algorithm); +// assertEquals("public-key-pem-2", storedKASInfo2.PublicKey); +// } +// +// GetAttributeValuesByFqnsResponse getResponseWithGrants(GetAttributeValuesByFqnsRequest req, +// List grants) { +// GetAttributeValuesByFqnsResponse.Builder builder = GetAttributeValuesByFqnsResponse.newBuilder(); +// +// for (String v : req.getFqnsList()) { +// AttributeValueFQN vfqn; +// try { +// vfqn = new AttributeValueFQN(v); +// } catch (Exception e) { +// return null; // Or throw the exception as needed +// } +// +// Value val = Value.newBuilder(mockValueFor(vfqn)).addAllGrants(grants).build(); +// +// builder.putFqnAttributeValues(v, GetAttributeValuesByFqnsResponse.AttributeAndValue.newBuilder() +// .setAttribute(val.getAttribute()) +// .setValue(val) +// .build()); +// } +// +// return builder.build(); +// } +// +// @Test +// void testKeyCacheFromGrants() throws InterruptedException, ExecutionException { +// // Create the KasPublicKey object +// KasPublicKey kasPublicKey1 = KasPublicKey.newBuilder() +// .setAlg(KasPublicKeyAlgEnum.KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP256R1) +// .setKid("test-kid") +// .setPem("public-key-pem") +// .build(); +// KasPublicKey kasPublicKey2 = KasPublicKey.newBuilder() +// .setAlg(KasPublicKeyAlgEnum.KAS_PUBLIC_KEY_ALG_ENUM_RSA_2048) +// .setKid("test-kid-2") +// .setPem("public-key-pem-2") +// .build(); +// +// // Add the KasPublicKey to a list +// List kasPublicKeys = new ArrayList<>(); +// kasPublicKeys.add(kasPublicKey1); +// kasPublicKeys.add(kasPublicKey2); +// +// // Create the KeyAccessServer object +// KeyAccessServer kas1 = KeyAccessServer.newBuilder() +// .setPublicKey(PublicKey.newBuilder() +// .setCached(KasPublicKeySet.newBuilder() +// .addAllKeys(kasPublicKeys) +// .build())) +// .setUri("https://example.com/kas") +// .build(); +// +//// AttributesServiceGrpc.AttributesServiceFutureStub attributeGrpcStub = mock( +//// AttributesServiceGrpc.AttributesServiceFutureStub.class); +//// lenient().when(attributeGrpcStub.getAttributeValuesByFqns(any(GetAttributeValuesByFqnsRequest.class))) +//// .thenAnswer( +//// invocation -> { +//// GetAttributeValuesByFqnsResponse resp = getResponseWithGrants( +//// (GetAttributeValuesByFqnsRequest) invocation.getArguments()[0], List.of(kas1)); +//// SettableFuture future = SettableFuture.create(); +//// future.set(resp); // Set the request as the future's result +//// return future; +//// }); +//// AttributesServiceClient attributeGrpcStub = mock(AttributesServiceClient.class); +//// lenient().when(attributeGrpcStub.getAttributeValuesByFqns(any(), any())) +//// .thenAnswer( +//// invocation -> { +//// GetAttributeValuesByFqnsResponse resp = getResponseWithGrants( +//// (GetAttributeValuesByFqnsRequest) invocation.getArguments()[0], List.of(kas1)); +//// SettableFuture future = SettableFuture.create(); +//// future.set(resp); // Set the request as the future's result +//// return future; +//// }); +// +//// KASKeyCache keyCache = new KASKeyCache(); +//// +//// Granter reasoner = Autoconfigure.newGranterFromService(attributeGrpcStub, keyCache, +//// List.of(clsS, rel2gbr, rel2usa, n2kHCS, n2kSI).toArray(new AttributeValueFQN[0])); +//// assertThat(reasoner).isNotNull(); +//// +//// // Verify that the key was stored in the cache +//// Config.KASInfo storedKASInfo = keyCache.get("https://example.com/kas", "ec:secp256r1"); +//// assertNotNull(storedKASInfo); +//// assertEquals("https://example.com/kas", storedKASInfo.URL); +//// assertEquals("test-kid", storedKASInfo.KID); +//// assertEquals("ec:secp256r1", storedKASInfo.Algorithm); +//// assertEquals("public-key-pem", storedKASInfo.PublicKey); +//// +//// Config.KASInfo storedKASInfo2 = keyCache.get("https://example.com/kas", "rsa:2048"); +//// assertNotNull(storedKASInfo2); +//// assertEquals("https://example.com/kas", storedKASInfo2.URL); +//// assertEquals("test-kid-2", storedKASInfo2.KID); +//// assertEquals("rsa:2048", storedKASInfo2.Algorithm); +//// assertEquals("public-key-pem-2", storedKASInfo2.PublicKey); +//// +// } +// +//} diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/KASClientTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/KASClientTest.java index 496f4708..0e245517 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/KASClientTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/KASClientTest.java @@ -1,5 +1,11 @@ package io.opentdf.platform.sdk; +import com.connectrpc.ProtocolClientConfig; +import com.connectrpc.extensions.GoogleJavaProtobufStrategy; +import com.connectrpc.impl.ProtocolClient; +import com.connectrpc.okhttp.ConnectOkHttpClient; +import com.connectrpc.protocols.GETConfiguration; +import com.connectrpc.protocols.NetworkProtocol; import com.google.gson.Gson; import com.google.protobuf.ByteString; import com.nimbusds.jose.JOSEException; @@ -7,22 +13,26 @@ import com.nimbusds.jose.crypto.RSASSAVerifier; import com.nimbusds.jose.jwk.RSAKey; import com.nimbusds.jwt.SignedJWT; -import io.grpc.ManagedChannel; -import io.grpc.ManagedChannelBuilder; import io.grpc.Server; import io.grpc.ServerBuilder; import io.grpc.stub.StreamObserver; +import io.opentdf.platform.kas.AccessServiceClient; import io.opentdf.platform.kas.AccessServiceGrpc; import io.opentdf.platform.kas.PublicKeyRequest; import io.opentdf.platform.kas.PublicKeyResponse; import io.opentdf.platform.kas.RewrapRequest; import io.opentdf.platform.kas.RewrapResponse; +import okhttp3.OkHttpClient; +import okhttp3.Protocol; +import org.junit.Ignore; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import java.io.IOException; import java.security.interfaces.RSAPublicKey; import java.text.ParseException; import java.util.Base64; +import java.util.List; import java.util.Random; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; @@ -32,10 +42,18 @@ public class KASClientTest { - private static final Function channelFactory = (String url) -> ManagedChannelBuilder - .forTarget(url) - .usePlaintext() - .build(); + Function aclientFactory = (String endpoint) -> { + var c = new OkHttpClient.Builder() + .protocols(List.of(Protocol.H2_PRIOR_KNOWLEDGE)) + .build(); + + var as = new ProtocolClient( + new ConnectOkHttpClient(c), + new ProtocolClientConfig(endpoint, new GoogleJavaProtobufStrategy(), NetworkProtocol.GRPC, null, GETConfiguration.Enabled.INSTANCE) + ); + + return new AccessServiceClient(as); + }; @Test void testGettingPublicKey() throws IOException { @@ -51,17 +69,14 @@ public void publicKey(PublicKeyRequest request, StreamObserver channelFactory = (String url) -> ManagedChannelBuilder - .forTarget(url) - .usePlaintext() - .build(); + var keypair = CryptoUtils.generateRSAKeypair(); var dpopKey = new RSAKey.Builder((RSAPublicKey) keypair.getPublic()).privateKey(keypair.getPrivate()) .build(); - try (var kas = new KASClient(channelFactory, dpopKey)) { + try (var kas = new KASClient(aclientFactory, dpopKey)) { Config.KASInfo kasInfo = new Config.KASInfo(); - kasInfo.URL = "localhost:" + rewrapServer.getPort(); + kasInfo.URL = "http://localhost:" + rewrapServer.getPort(); assertThat(kas.getPublicKey(kasInfo).PublicKey).isEqualTo("тај је клуц"); } } finally { @@ -85,18 +100,16 @@ public void publicKey(PublicKeyRequest request, StreamObserver channelFactory = (String url) -> ManagedChannelBuilder - .forTarget(url) - .usePlaintext() - .build(); var keypair = CryptoUtils.generateRSAKeypair(); var dpopKey = new RSAKey.Builder((RSAPublicKey) keypair.getPublic()).privateKey(keypair.getPrivate()) .build(); - try (var kas = new KASClient(channelFactory, dpopKey)) { + try (var kas = new KASClient(aclientFactory, dpopKey)) { Config.KASInfo kasInfo = new Config.KASInfo(); - kasInfo.URL = "localhost:" + server.getPort(); + kasInfo.URL = "http://localhost:" + server.getPort(); assertThat(kas.getPublicKey(kasInfo).KID).isEqualTo("r1"); + } catch (Exception e) { + throw e; } } finally { if (server != null) { @@ -156,10 +169,10 @@ public void rewrap(RewrapRequest request, StreamObserver respons rewrapServer = startServer(accessService); byte[] plaintextKey; byte[] rewrapResponse; - try (var kas = new KASClient(channelFactory, dpopKey)) { + try (var kas = new KASClient(aclientFactory, dpopKey)) { Manifest.KeyAccess keyAccess = new Manifest.KeyAccess(); - keyAccess.url = "localhost:" + rewrapServer.getPort(); + keyAccess.url = "http://localhost:" + rewrapServer.getPort(); plaintextKey = new byte[32]; new Random().nextBytes(plaintextKey); var serverWrappedKey = new AsymEncryption(serverKeypair.getPublic()).encrypt(plaintextKey); @@ -175,7 +188,7 @@ public void rewrap(RewrapRequest request, StreamObserver respons } } - @Test + @Test @Disabled("not quite sure what are address normalization requirements are now") public void testAddressNormalization() { var lastAddress = new AtomicReference(); var dpopKeypair = CryptoUtils.generateRSAKeypair(); @@ -183,7 +196,7 @@ public void testAddressNormalization() { .build(); var kasClient = new KASClient(addr -> { lastAddress.set(addr); - return ManagedChannelBuilder.forTarget(addr).build(); + return aclientFactory.apply(addr); }, dpopKey); var stub = kasClient.getStub("http://localhost:8080"); diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/SDKBuilderTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/SDKBuilderTest.java index 1f1af97e..55aef88d 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/SDKBuilderTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/SDKBuilderTest.java @@ -29,7 +29,10 @@ import org.apache.commons.io.IOUtils; import org.junit.jupiter.api.Test; -import java.io.*; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; import java.net.InetAddress; import java.net.ServerSocket; import java.nio.charset.StandardCharsets; @@ -39,7 +42,6 @@ import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.Base64; -import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicReference; import static org.assertj.core.api.Assertions.assertThat; @@ -183,6 +185,16 @@ public void getWellKnownConfiguration(GetWellKnownConfigurationRequest request, .directExecutor() .addService(wellKnownService) .addService(new NamespaceServiceGrpc.NamespaceServiceImplBase() { + @Override + public void getNamespace(GetNamespaceRequest request, + StreamObserver responseObserver) { + var val = Value.newBuilder().setStringValue(issuer).build(); + var response = GetNamespaceResponse + .newBuilder() + .build(); + responseObserver.onNext(response); + responseObserver.onCompleted(); + } }) .intercept(new ServerInterceptor() { @Override @@ -236,7 +248,7 @@ public ServerCall.Listener interceptCall(ServerCall ServerCall.Listener interceptCall(ServerCall ServerCall.Listener interceptCall(ServerCall Date: Sun, 20 Apr 2025 22:21:22 -0400 Subject: [PATCH 07/52] get tests working --- pom.xml | 4 +- sdk/pom.xml | 104 +- .../opentdf/platform/sdk/Autoconfigure.java | 11 +- .../io/opentdf/platform/sdk/SDKBuilder.java | 3 +- .../opentdf/platform/sdk/connectrpc/Cont.kt | 25 - .../io/opentdf/platform/AuthInterceptor.kt | 8 +- .../kotlin/io/opentdf/platform/Helpers.kt | 36 - .../platform/sdk/AutoconfigureTest.java | 1851 +++++++++-------- .../opentdf/platform/sdk/SDKBuilderTest.java | 6 +- 9 files changed, 1024 insertions(+), 1024 deletions(-) delete mode 100644 sdk/src/main/java/io/opentdf/platform/sdk/connectrpc/Cont.kt delete mode 100644 sdk/src/main/kotlin/io/opentdf/platform/Helpers.kt diff --git a/pom.xml b/pom.xml index 75186079..d9b3fd3b 100644 --- a/pom.xml +++ b/pom.xml @@ -16,7 +16,7 @@ 11 2.20.0 1.68.0 - 3.25.3 + 4.29.2 8.3.5 @@ -123,7 +123,7 @@ com.google.protobuf protobuf-java - 3.25.5 + ${protobuf.version} org.slf4j diff --git a/sdk/pom.xml b/sdk/pom.xml index 78876a59..6a46ae79 100644 --- a/sdk/pom.xml +++ b/sdk/pom.xml @@ -12,7 +12,9 @@ 0.22.1 https://github.com/CodeIntelligenceTesting/jazzer/releases/download/v${jazzer.version} - 2.1.20 + 2.1.0 + 0.7.2 + 4.12.0 @@ -55,30 +57,6 @@ commons-codec 1.17.0 - - com.connectrpc - connect-kotlin - 0.7.2 - - - org.jetbrains.kotlin - kotlin-stdlib - - - com.squareup.okio - okio-jvm - - - org.jetbrains - annotations - - - - - com.squareup.okio - okio-jvm - 3.9.1 - io.github.erdtman java-json-canonicalization @@ -106,17 +84,71 @@ com.squareup.okhttp3 okhttp - 4.12.0 + ${okhttp.version} + + + com.squareup.okio + okio-jvm + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + + - com.connectrpc - connect-kotlin-google-java-ext - 0.7.2 + com.squareup.okio + okio-jvm + 3.11.0 + + + org.jetbrains.kotlin + kotlin-stdlib + + com.connectrpc connect-kotlin-okhttp - 0.7.2 + ${connect.version} + + + org.jetbrains.kotlin + kotlin-reflect + + + com.squareup.okio + okio-jvm + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + + + org.jetbrains + annotations + + + + + com.connectrpc + connect-kotlin-google-java-ext + ${connect.version} + + + com.google.code.gson + gson + + + com.google.j2objc + j2objc-annotations + + + com.squareup.okio + okio-jvm + + + @@ -162,13 +194,17 @@ com.squareup.okhttp3 mockwebserver - 4.12.0 + ${okhttp.version} test com.squareup.okio okio-jvm + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + @@ -199,6 +235,10 @@ com.squareup.okio okio-jvm + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + @@ -258,7 +298,7 @@ - src/main/java + src/main/kotlin target/generated-sources diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/Autoconfigure.java b/sdk/src/main/java/io/opentdf/platform/sdk/Autoconfigure.java index 5398b0e4..08c98f91 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/Autoconfigure.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/Autoconfigure.java @@ -1,5 +1,7 @@ package io.opentdf.platform.sdk; +import com.connectrpc.ResponseMessage; +import com.connectrpc.ResponseMessageKt; import io.opentdf.platform.policy.Attribute; import io.opentdf.platform.policy.AttributeRuleTypeEnum; import io.opentdf.platform.policy.AttributeValueSelector; @@ -29,7 +31,6 @@ import java.util.Objects; import java.util.Set; import java.util.StringJoiner; -import java.util.concurrent.ExecutionException; import java.util.function.Supplier; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -699,14 +700,16 @@ public static Granter newGranterFromAttributes(Value... attrValues) throws AutoC } // Gets a list of directory of KAS grants for a list of attribute FQNs - public static Granter newGranterFromService(AttributesServiceClient as, KASKeyCache keyCache, AttributeValueFQN... fqns) throws AutoConfigureException, ExecutionException, InterruptedException { - + public static Granter newGranterFromService(AttributesServiceClient as, KASKeyCache keyCache, AttributeValueFQN... fqns) throws AutoConfigureException { GetAttributeValuesByFqnsRequest request = GetAttributeValuesByFqnsRequest.newBuilder() .addAllFqns(Arrays.stream(fqns).map(AttributeValueFQN::toString).collect(Collectors.toList())) .setWithValue(AttributeValueSelector.newBuilder().setWithKeyAccessGrants(true).build()) .build(); - GetAttributeValuesByFqnsResponse av = Helpers.getAttributeValuesByFqns(as, request); + GetAttributeValuesByFqnsResponse av = ResponseMessageKt.getOrThrow( + as.getAttributeValuesByFqnsBlocking(request, Collections.emptyMap()).execute() + ); + return getGranter(keyCache, new ArrayList<>(av.getFqnAttributeValuesMap().values())); } diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java b/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java index 5183f04b..6771198c 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java @@ -2,6 +2,7 @@ import com.connectrpc.Interceptor; import com.connectrpc.ProtocolClientConfig; +import com.connectrpc.ResponseMessageKt; import com.connectrpc.extensions.GoogleJavaProtobufStrategy; import com.connectrpc.impl.ProtocolClient; import com.connectrpc.okhttp.ConnectOkHttpClient; @@ -166,7 +167,7 @@ private Interceptor getAuthInterceptor(RSAKey rsaKey) { bootstrapClient = getProtocolClient(platformEndpoint) ; var stub = new WellKnownServiceClient(bootstrapClient); try { - config = Helpers.call(stub.getWellKnownConfigurationBlocking(GetWellKnownConfigurationRequest.getDefaultInstance(), Collections.emptyMap())); + config = ResponseMessageKt.getOrThrow(stub.getWellKnownConfigurationBlocking(GetWellKnownConfigurationRequest.getDefaultInstance(), Collections.emptyMap()).execute()); } catch (StatusRuntimeException e) { Status status = Status.fromThrowable(e); throw new SDKException(String.format("Got grpc status [%s] when getting configuration", status), e); diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/connectrpc/Cont.kt b/sdk/src/main/java/io/opentdf/platform/sdk/connectrpc/Cont.kt deleted file mode 100644 index 4e9bfce3..00000000 --- a/sdk/src/main/java/io/opentdf/platform/sdk/connectrpc/Cont.kt +++ /dev/null @@ -1,25 +0,0 @@ -package io.opentdf.platform.sdk.connectrpc - -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.Dispatchers -import java.util.function.BiConsumer -import kotlin.coroutines.Continuation -import kotlin.coroutines.CoroutineContext -interface Cont { - companion object { - @JvmOverloads - fun getContinuation( - onFinished: BiConsumer, - dispatcher: CoroutineDispatcher = Dispatchers.Default - ): Continuation { - return object : Continuation { - override val context: CoroutineContext - get() = dispatcher - - override fun resumeWith(result: Result) { - onFinished.accept(result.getOrNull(), result.exceptionOrNull()) - } - } - } - } -} diff --git a/sdk/src/main/kotlin/io/opentdf/platform/AuthInterceptor.kt b/sdk/src/main/kotlin/io/opentdf/platform/AuthInterceptor.kt index 5da64919..c7eaa3ad 100644 --- a/sdk/src/main/kotlin/io/opentdf/platform/AuthInterceptor.kt +++ b/sdk/src/main/kotlin/io/opentdf/platform/AuthInterceptor.kt @@ -23,12 +23,8 @@ private class AuthInterceptor(private val ts: TokenSource) : Interceptor{ methodSpec = request.methodSpec, ) }, - requestBodyFunction = { resp -> - resp - }, - streamResultFunction = { streamResult -> - streamResult - }, + requestBodyFunction = { resp -> resp }, + streamResultFunction = { streamResult -> streamResult }, ) } diff --git a/sdk/src/main/kotlin/io/opentdf/platform/Helpers.kt b/sdk/src/main/kotlin/io/opentdf/platform/Helpers.kt deleted file mode 100644 index e4b0dbf0..00000000 --- a/sdk/src/main/kotlin/io/opentdf/platform/Helpers.kt +++ /dev/null @@ -1,36 +0,0 @@ -package io.opentdf.platform.sdk - -import com.connectrpc.UnaryBlockingCall -import com.connectrpc.getOrThrow -import io.opentdf.platform.policy.attributes.AttributesServiceClient -import io.opentdf.platform.policy.attributes.GetAttributeValuesByFqnsRequest -import io.opentdf.platform.policy.attributes.GetAttributeValuesByFqnsResponse -import io.opentdf.platform.policy.namespaces.GetNamespaceRequest -import io.opentdf.platform.policy.namespaces.NamespaceServiceClient -import kotlinx.coroutines.runBlocking - -class Helpers { - companion object { - @JvmStatic - fun call(ub: UnaryBlockingCall): T { - return ub.execute().getOrThrow(); - } - - @JvmStatic - fun noHeaders(): Map { - return emptyMap() - } - - @JvmStatic - fun getNamespace(nsc: NamespaceServiceClient, req: GetNamespaceRequest) { - return runBlocking { - nsc.getNamespace(req, emptyMap()).getOrThrow(); - } - } - - @JvmStatic - fun getAttributeValuesByFqns(nsc: AttributesServiceClient, req: GetAttributeValuesByFqnsRequest): GetAttributeValuesByFqnsResponse { - return nsc.getAttributeValuesByFqnsBlocking(req, emptyMap()).execute().getOrThrow(); - } - } -} \ No newline at end of file diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/AutoconfigureTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/AutoconfigureTest.java index 883115e9..79a62d1e 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/AutoconfigureTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/AutoconfigureTest.java @@ -1,581 +1,601 @@ -//package io.opentdf.platform.sdk; -// -//import static org.assertj.core.api.Assertions.assertThat; -//import static org.assertj.core.api.Assertions.assertThatThrownBy; -//import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -//import static org.junit.jupiter.api.Assertions.assertEquals; -//import static org.junit.jupiter.api.Assertions.assertNotNull; -//import static org.mockito.ArgumentMatchers.any; -//import static org.mockito.Mockito.lenient; -//import static org.mockito.Mockito.mock; -//import static org.mockito.Mockito.never; -//import static org.mockito.Mockito.verify; -// -//import io.opentdf.platform.policy.Attribute; -//import io.opentdf.platform.policy.Value; -//import io.opentdf.platform.policy.attributes.AttributesServiceClient; -//import io.opentdf.platform.policy.attributes.AttributesServiceGrpc; -//import io.opentdf.platform.policy.attributes.GetAttributeValuesByFqnsRequest; -//import io.opentdf.platform.policy.attributes.GetAttributeValuesByFqnsResponse; -//import io.opentdf.platform.sdk.Autoconfigure.AttributeValueFQN; -//import io.opentdf.platform.sdk.Autoconfigure.Granter.AttributeBooleanExpression; -//import io.opentdf.platform.sdk.Autoconfigure.Granter.BooleanKeyExpression; -//import io.opentdf.platform.sdk.Autoconfigure.KeySplitStep; -//import io.opentdf.platform.sdk.Autoconfigure.Granter; -//import io.opentdf.platform.policy.Namespace; -//import io.opentdf.platform.policy.PublicKey; -//import io.opentdf.platform.policy.KeyAccessServer; -//import io.opentdf.platform.policy.AttributeRuleTypeEnum; -//import io.opentdf.platform.policy.KasPublicKey; -//import io.opentdf.platform.policy.KasPublicKeyAlgEnum; -//import io.opentdf.platform.policy.KasPublicKeySet; -// -//import org.junit.jupiter.api.BeforeAll; -//import org.junit.jupiter.api.Test; -//import org.mockito.Mockito; -// -//import com.google.common.util.concurrent.SettableFuture; -// -//import java.util.ArrayList; -//import java.util.HashSet; -//import java.util.List; -//import java.util.Set; -//import java.util.concurrent.ExecutionException; -//import java.util.stream.Collectors; -//import java.util.regex.Matcher; -//import java.util.regex.Pattern; -// -//public class AutoconfigureTest { -// -// private static final String KAS_AU = "https://kas.au/"; -// private static final String KAS_CA = "https://kas.ca/"; -// private static final String KAS_UK = "https://kas.uk/"; -// private static final String KAS_NZ = "https://kas.nz/"; -// private static final String KAS_US = "https://kas.us/"; -// private static final String KAS_US_HCS = "https://hcs.kas.us/"; -// private static final String KAS_US_SA = "https://si.kas.us/"; -// public static final String SPECIFIED_KAS = "https://attr.kas.com/"; -// public static final String EVEN_MORE_SPECIFIC_KAS = "https://value.kas.com/"; -// private static final String NAMESPACE_KAS = "https://namespace.kas.com/"; -// private static Autoconfigure.AttributeNameFQN SPKSPECKED; -// private static Autoconfigure.AttributeNameFQN SPKUNSPECKED; -// -// private static Autoconfigure.AttributeNameFQN CLS; -// private static Autoconfigure.AttributeNameFQN N2K; -// private static Autoconfigure.AttributeNameFQN REL; -// private static Autoconfigure.AttributeNameFQN UNSPECKED; -// private static Autoconfigure.AttributeNameFQN SPECKED; -// -// private static Autoconfigure.AttributeValueFQN clsA; -// private static Autoconfigure.AttributeValueFQN clsS; -// private static Autoconfigure.AttributeValueFQN clsTS; -// private static Autoconfigure.AttributeValueFQN n2kHCS; -// private static Autoconfigure.AttributeValueFQN n2kInt; -// private static Autoconfigure.AttributeValueFQN n2kSI; -// private static Autoconfigure.AttributeValueFQN rel2can; -// private static Autoconfigure.AttributeValueFQN rel2gbr; -// private static Autoconfigure.AttributeValueFQN rel2nzl; -// private static Autoconfigure.AttributeValueFQN rel2usa; -// private static Autoconfigure.AttributeValueFQN uns2uns; -// private static Autoconfigure.AttributeValueFQN uns2spk; -// private static Autoconfigure.AttributeValueFQN spk2uns; -// private static Autoconfigure.AttributeValueFQN spk2spk; -// private static AttributeValueFQN spk2uns2uns; -// private static AttributeValueFQN spk2uns2spk; -// private static Autoconfigure.AttributeValueFQN spk2spk2uns; -// private static Autoconfigure.AttributeValueFQN spk2spk2spk; -// -// @BeforeAll -// public static void setup() throws AutoConfigureException { -// // Initialize the FQNs (Fully Qualified Names) -// CLS = new Autoconfigure.AttributeNameFQN("https://virtru.com/attr/Classification"); -// N2K = new Autoconfigure.AttributeNameFQN("https://virtru.com/attr/Need%20to%20Know"); -// REL = new Autoconfigure.AttributeNameFQN("https://virtru.com/attr/Releasable%20To"); -// UNSPECKED = new Autoconfigure.AttributeNameFQN("https://other.com/attr/unspecified"); -// SPECKED = new Autoconfigure.AttributeNameFQN("https://other.com/attr/specified"); -// SPKUNSPECKED = new Autoconfigure.AttributeNameFQN("https://hasgrants.com/attr/unspecified"); -// SPKSPECKED = new Autoconfigure.AttributeNameFQN("https://hasgrants.com/attr/specified"); -// -// clsA = new Autoconfigure.AttributeValueFQN("https://virtru.com/attr/Classification/value/Allowed"); -// clsS = new Autoconfigure.AttributeValueFQN("https://virtru.com/attr/Classification/value/Secret"); -// clsTS = new Autoconfigure.AttributeValueFQN("https://virtru.com/attr/Classification/value/Top%20Secret"); -// -// n2kHCS = new Autoconfigure.AttributeValueFQN("https://virtru.com/attr/Need%20to%20Know/value/HCS"); -// n2kInt = new Autoconfigure.AttributeValueFQN("https://virtru.com/attr/Need%20to%20Know/value/INT"); -// n2kSI = new Autoconfigure.AttributeValueFQN("https://virtru.com/attr/Need%20to%20Know/value/SI"); -// -// rel2can = new Autoconfigure.AttributeValueFQN("https://virtru.com/attr/Releasable%20To/value/CAN"); -// rel2gbr = new Autoconfigure.AttributeValueFQN("https://virtru.com/attr/Releasable%20To/value/GBR"); -// rel2nzl = new Autoconfigure.AttributeValueFQN("https://virtru.com/attr/Releasable%20To/value/NZL"); -// rel2usa = new Autoconfigure.AttributeValueFQN("https://virtru.com/attr/Releasable%20To/value/USA"); -// -// uns2uns = new Autoconfigure.AttributeValueFQN("https://other.com/attr/unspecified/value/unspecked"); -// uns2spk = new Autoconfigure.AttributeValueFQN("https://other.com/attr/unspecified/value/specked"); -// spk2uns = new Autoconfigure.AttributeValueFQN("https://other.com/attr/specified/value/unspecked"); -// spk2spk = new Autoconfigure.AttributeValueFQN("https://other.com/attr/specified/value/specked"); -// -// spk2uns2uns = new Autoconfigure.AttributeValueFQN("https://hasgrants.com/attr/unspecified/value/unspecked"); -// spk2uns2spk = new Autoconfigure.AttributeValueFQN("https://hasgrants.com/attr/unspecified/value/specked"); -// spk2spk2uns = new Autoconfigure.AttributeValueFQN("https://hasgrants.com/attr/specified/value/unspecked"); -// spk2spk2spk = new Autoconfigure.AttributeValueFQN("https://hasgrants.com/attr/specified/value/specked"); -// } -// -// private static String spongeCase(String s) { -// final Pattern PATTERN = Pattern.compile("^(https?://[\\w./]+/attr/)([^/]*)(/value/)?(\\S*)?$"); -// Matcher matcher = PATTERN.matcher(s); -// -// if (!matcher.matches()) { -// throw new IllegalArgumentException("Invalid input string"); -// } -// -// StringBuilder sb = new StringBuilder(); -// sb.append(matcher.group(1)); -// -// String n = matcher.group(2); -// for (int i = 0; i < n.length(); i++) { -// String sub = n.substring(i, i + 1); -// if ((i & 1) == 1) { -// sb.append(sub.toUpperCase()); -// } else { -// sb.append(sub); -// } -// } -// -// if (matcher.group(3) != null) { -// sb.append(matcher.group(3)); -// String v = matcher.group(4); -// for (int i = 0; i < v.length(); i++) { -// String sub = v.substring(i, i + 1); -// if ((i & 1) == 1) { -// sb.append(sub); -// } else { -// sb.append(sub.toUpperCase()); -// } -// } -// } -// return sb.toString(); -// } -// -// private List valuesToPolicy(AttributeValueFQN... p) throws AutoConfigureException { -// List values = new ArrayList<>(); -// for (AttributeValueFQN afqn : List.of(p)) { -// values.add(mockValueFor(afqn)); -// } -// return values; -// } -// -// private List policyToStringKeys(List policy) { -// return policy.stream() -// .map(AttributeValueFQN::getKey) -// .collect(Collectors.toList()); -// } -// -// private Autoconfigure.AttributeValueFQN messUpV(Autoconfigure.AttributeValueFQN a) { -// try { -// return new Autoconfigure.AttributeValueFQN(spongeCase(a.toString())); -// } catch (Exception e) { -// throw new RuntimeException("Failed to create AttributeValueFQN", e); -// } -// } -// -// private Attribute mockAttributeFor(Autoconfigure.AttributeNameFQN fqn) { -// Namespace ns1 = Namespace.newBuilder().setId("v").setName("virtru.com").setFqn("https://virtru.com").build(); -// Namespace ns2 = Namespace.newBuilder().setId("o").setName("other.com").setFqn("https://other.com").build(); -// Namespace ns3 = Namespace.newBuilder().setId("h").setName("hasgrants.com").addGrants(KeyAccessServer.newBuilder().setUri(NAMESPACE_KAS).build()).setFqn("https://hasgrants.com").build(); -// -// String key = fqn.getKey(); -// if (key.equals(CLS.getKey())) { -// return Attribute.newBuilder().setId("CLS").setNamespace(ns1) -// .setName("Classification").setRule(AttributeRuleTypeEnum.ATTRIBUTE_RULE_TYPE_ENUM_HIERARCHY) -// .setFqn(fqn.toString()).build(); -// } else if (key.equals(N2K.getKey())) { -// return Attribute.newBuilder().setId("N2K").setNamespace(ns1) -// .setName("Need to Know").setRule(AttributeRuleTypeEnum.ATTRIBUTE_RULE_TYPE_ENUM_ALL_OF) -// .setFqn(fqn.toString()).build(); -// } else if (key.equals(REL.getKey())) { -// return Attribute.newBuilder().setId("REL").setNamespace(ns1) -// .setName("Releasable To").setRule(AttributeRuleTypeEnum.ATTRIBUTE_RULE_TYPE_ENUM_ANY_OF) -// .setFqn(fqn.toString()).build(); -// } else if (key.equals(SPECKED.getKey())) { -// return Attribute.newBuilder().setId("SPK").setNamespace(ns2) -// .setName("specified").setRule(AttributeRuleTypeEnum.ATTRIBUTE_RULE_TYPE_ENUM_ANY_OF) -// .setFqn(fqn.toString()) -// .addGrants(KeyAccessServer.newBuilder().setUri(SPECIFIED_KAS).build()) -// .build(); -// } else if (key.equals(UNSPECKED.getKey())) { -// return Attribute.newBuilder().setId("UNS").setNamespace(ns2) -// .setName("unspecified").setRule(AttributeRuleTypeEnum.ATTRIBUTE_RULE_TYPE_ENUM_ANY_OF) -// .setFqn(fqn.toString()).build(); -// } else if (key.equals(SPKSPECKED.getKey())) { -// return Attribute.newBuilder().setId("SPKSPK").setNamespace(ns3) -// .setName("specified").setRule(AttributeRuleTypeEnum.ATTRIBUTE_RULE_TYPE_ENUM_ANY_OF) -// .addGrants(KeyAccessServer.newBuilder().setUri(SPECIFIED_KAS).build()) -// .setName(fqn.toString()) -// .build(); -// } else if (key.equals(SPKUNSPECKED.getKey())) { -// return Attribute.newBuilder().setId("SPKUNSPK").setNamespace(ns3) -// .setName("unspecified").setRule(AttributeRuleTypeEnum.ATTRIBUTE_RULE_TYPE_ENUM_ANY_OF) -// .setName(fqn.toString()) -// .build(); -// } -// -// throw new IllegalArgumentException("Key not recognized: " + key); -// } -// -// private Value mockValueFor(Autoconfigure.AttributeValueFQN fqn) throws AutoConfigureException { -// Autoconfigure.AttributeNameFQN an = fqn.prefix(); -// Attribute a = mockAttributeFor(an); -// String v = fqn.value(); -// Value p = Value.newBuilder() -// .setId(a.getId() + ":" + v) -// .setAttribute(a) -// .setValue(v) -// .setFqn(fqn.toString()) -// .build(); -// -// if (an.getKey().equals(N2K.getKey())) { -// switch (v.toUpperCase()) { -// case "INT": -// p = p.toBuilder().addGrants(KeyAccessServer.newBuilder().setUri(KAS_UK).build()).build(); -// break; -// case "HCS": -// p = p.toBuilder().addGrants(KeyAccessServer.newBuilder().setUri(KAS_US_HCS).build()).build(); -// break; -// case "SI": -// p = p.toBuilder().addGrants(KeyAccessServer.newBuilder().setUri(KAS_US_SA).build()).build(); -// break; -// } -// } else if (an.getKey().equals(REL.getKey())) { -// switch (v.toUpperCase()) { -// case "FVEY": -// p = p.toBuilder().addGrants(KeyAccessServer.newBuilder().setUri(KAS_AU).build()) -// .addGrants(1, KeyAccessServer.newBuilder().setUri(KAS_CA).build()) -// .addGrants(1, KeyAccessServer.newBuilder().setUri(KAS_UK).build()) -// .addGrants(1, KeyAccessServer.newBuilder().setUri(KAS_NZ).build()) -// .addGrants(1, KeyAccessServer.newBuilder().setUri(KAS_US).build()) -// .build(); -// break; -// case "AUS": -// p = p.toBuilder().addGrants(KeyAccessServer.newBuilder().setUri(KAS_AU).build()) -// .build(); -// break; -// case "CAN": -// p = p.toBuilder().addGrants(0, KeyAccessServer.newBuilder().setUri(KAS_CA).build()) -// .build(); -// break; -// case "GBR": -// p = p.toBuilder().addGrants(KeyAccessServer.newBuilder().setUri(KAS_UK).build()) -// .build(); -// break; -// case "NZL": -// p = p.toBuilder().addGrants(KeyAccessServer.newBuilder().setUri(KAS_NZ).build()) -// .build(); -// break; -// case "USA": -// p = p.toBuilder().addGrants(KeyAccessServer.newBuilder().setUri(KAS_US).build()) -// .build(); -// break; -// } -// } else if (an.getKey().equals(CLS.getKey())) { -// // defaults only -// } else if (List.of(SPECKED.getKey(), SPKSPECKED.getKey()).contains(an.getKey())) { -// if (fqn.value().equalsIgnoreCase("specked")) { -// p = p.toBuilder().addGrants(KeyAccessServer.newBuilder().setUri(EVEN_MORE_SPECIFIC_KAS).build()) -// .build(); -// } -// } else if (List.of(UNSPECKED.getKey(), SPKUNSPECKED.getKey()).contains(an.getKey())) { -// if (fqn.value().equalsIgnoreCase("specked")) { -// p = p.toBuilder().addGrants(KeyAccessServer.newBuilder().setUri(EVEN_MORE_SPECIFIC_KAS).build()) -// .build(); -// } -// } -// return p; -// } -// -// @Test -// public void testAttributeFromURL() throws AutoConfigureException { -// for (TestCase tc : List.of( -// new TestCase("letter", "https://e/attr/a", "https://e", "a"), -// new TestCase("number", "https://e/attr/1", "https://e", "1"), -// new TestCase("emoji", "https://e/attr/%F0%9F%98%81", "https://e", "😁"), -// new TestCase("dash", "https://a-b.com/attr/b-c", "https://a-b.com", "b-c"))) { -// Autoconfigure.AttributeNameFQN a = new Autoconfigure.AttributeNameFQN(tc.getU()); -// assertThat(a.authority()).isEqualTo(tc.getAuth()); -// assertThat(a.name()).isEqualTo(tc.getName()); -// } -// } -// -// @Test -// public void testAttributeFromMalformedURL() { -// for (TestCase tc : List.of( -// new TestCase("no name", "https://e/attr"), -// new TestCase("invalid prefix 1", "hxxp://e/attr/a"), -// new TestCase("invalid prefix 2", "e/attr/a"), -// new TestCase("invalid prefix 3", "file://e/attr/a"), -// new TestCase("invalid prefix 4", "https:///attr/a"), -// new TestCase("bad encoding", "https://a/attr/%😁"), -// new TestCase("with value", "https://e/attr/a/value/b"))) { -// assertThatThrownBy(() -> new Autoconfigure.AttributeNameFQN(tc.getU())) -// .isInstanceOf(AutoConfigureException.class); -// } -// } -// -// @Test -// public void testAttributeValueFromURL() { -// List testCases = List.of( -// new TestCase("number", "https://e/attr/a/value/1", "https://e", "a", "1"), -// new TestCase("space", "https://e/attr/a/value/%20", "https://e", "a", " "), -// new TestCase("emoji", "https://e/attr/a/value/%F0%9F%98%81", "https://e", "a", "😁"), -// new TestCase("numberdef", "https://e/attr/1/value/one", "https://e", "1", "one"), -// new TestCase("valuevalue", "https://e/attr/value/value/one", "https://e", "value", "one"), -// new TestCase("dash", "https://a-b.com/attr/b-c/value/c-d", "https://a-b.com", "b-c", "c-d")); -// -// for (TestCase tc : testCases) { -// assertDoesNotThrow(() -> { -// AttributeValueFQN a = new AttributeValueFQN(tc.getU()); -// assertThat(a.authority()).isEqualTo(tc.getAuth()); -// assertThat(a.name()).isEqualTo(tc.getName()); -// assertThat(a.value()).isEqualTo(tc.getValue()); -// }); -// } -// } -// -// @Test -// public void testAttributeValueFromMalformedURL() { -// List testCases = List.of( -// new TestCase("no name", "https://e/attr/value/1"), -// new TestCase("no value", "https://e/attr/who/value"), -// new TestCase("invalid prefix 1", "hxxp://e/attr/a/value/1"), -// new TestCase("invalid prefix 2", "e/attr/a/a/value/1"), -// new TestCase("bad encoding", "https://a/attr/emoji/value/%😁")); -// -// for (TestCase tc : testCases) { -// assertThatThrownBy(() -> new AttributeValueFQN(tc.getU())) -// .isInstanceOf(AutoConfigureException.class) -// .hasMessageContaining("invalid type"); -// } -// } -// -// @Test -// public void testConfigurationServicePutGet() { -// List testCases = List.of( -// new ConfigurationTestCase("default", List.of(clsA), 1, List.of()), -// new ConfigurationTestCase("one-country", List.of(rel2gbr), 1, List.of(KAS_UK)), -// new ConfigurationTestCase("two-country", List.of(rel2gbr, rel2nzl), 2, List.of(KAS_UK, KAS_NZ)), -// new ConfigurationTestCase("with-default", List.of(clsA, rel2gbr), 2, List.of(KAS_UK)), -// new ConfigurationTestCase("need-to-know", List.of(clsTS, rel2usa, n2kSI), 3, -// List.of(KAS_US, KAS_US_SA))); -// -// for (ConfigurationTestCase tc : testCases) { -// assertDoesNotThrow(() -> { -// List v = valuesToPolicy(tc.getPolicy().toArray(new AttributeValueFQN[0])); -// Granter grants = Autoconfigure.newGranterFromAttributes(v.toArray(new Value[0])); -// assertThat(grants).isNotNull(); -// assertThat(grants.getGrants()).hasSize(tc.getSize()); -// assertThat(policyToStringKeys(tc.getPolicy())).containsAll(grants.getGrants().keySet()); -// Set actualKases = new HashSet<>(); -// for (Autoconfigure.KeyAccessGrant g : grants.getGrants().values()) { -// assertThat(g).isNotNull(); -// for (String k : g.kases) { -// actualKases.add(k); -// } -// } -// -// String[] kasArray = tc.getKases().toArray(new String[tc.getKases().size()]); -// assertThat(actualKases).containsExactlyInAnyOrder(kasArray); -// }); -// } -// } -// -// @Test -// public void testReasonerConstructAttributeBoolean() { -// List testCases = List.of( -// new ReasonerTestCase( -// "one actual with default", -// List.of(clsS, rel2can), -// List.of(KAS_US), -// "https://virtru.com/attr/Classification/value/Secret&https://virtru.com/attr/Releasable%20To/value/CAN", -// "[DEFAULT]&(https://kas.ca/)", -// "(https://kas.ca/)", -// List.of(new KeySplitStep(KAS_CA, ""))), -// new ReasonerTestCase( -// "one defaulted attr", -// List.of(clsS), -// List.of(KAS_US), -// "https://virtru.com/attr/Classification/value/Secret", -// "[DEFAULT]", -// "", -// List.of(new KeySplitStep(KAS_US, ""))), -// new ReasonerTestCase( -// "empty policy", -// List.of(), -// List.of(KAS_US), -// "∅", -// "", -// "", -// List.of(new KeySplitStep(KAS_US, ""))), -// new ReasonerTestCase( -// "old school splits", -// List.of(), -// List.of(KAS_AU, KAS_CA, KAS_US), -// "∅", -// "", -// "", -// List.of(new KeySplitStep(KAS_AU, "1"), new KeySplitStep(KAS_CA, "2"), -// new KeySplitStep(KAS_US, "3"))), -// new ReasonerTestCase( -// "simple with all three ops", -// List.of(clsS, rel2gbr, n2kInt), -// List.of(KAS_US), -// "https://virtru.com/attr/Classification/value/Secret&https://virtru.com/attr/Releasable%20To/value/GBR&https://virtru.com/attr/Need%20to%20Know/value/INT", -// "[DEFAULT]&(https://kas.uk/)&(https://kas.uk/)", -// "(https://kas.uk/)", -// List.of(new KeySplitStep(KAS_UK, ""))), -// new ReasonerTestCase( -// "compartments", -// List.of(clsS, rel2gbr, rel2usa, n2kHCS, n2kSI), -// List.of(KAS_US), -// "https://virtru.com/attr/Classification/value/Secret&https://virtru.com/attr/Releasable%20To/value/{GBR,USA}&https://virtru.com/attr/Need%20to%20Know/value/{HCS,SI}", -// "[DEFAULT]&(https://kas.uk/⋁https://kas.us/)&(https://hcs.kas.us/⋀https://si.kas.us/)", -// "(https://kas.uk/⋁https://kas.us/)&(https://hcs.kas.us/)&(https://si.kas.us/)", -// List.of(new KeySplitStep(KAS_UK, "1"), new KeySplitStep(KAS_US, "1"), -// new KeySplitStep(KAS_US_HCS, "2"), new KeySplitStep(KAS_US_SA, "3"))), -// new ReasonerTestCase( -// "compartments - case insensitive", -// List.of( -// messUpV(clsS), messUpV(rel2gbr), messUpV(rel2usa), messUpV(n2kHCS), messUpV(n2kSI)), -// List.of(KAS_US), -// "https://virtru.com/attr/Classification/value/Secret&https://virtru.com/attr/Releasable%20To/value/{GBR,USA}&https://virtru.com/attr/Need%20to%20Know/value/{HCS,SI}", -// "[DEFAULT]&(https://kas.uk/⋁https://kas.us/)&(https://hcs.kas.us/⋀https://si.kas.us/)", -// "(https://kas.uk/⋁https://kas.us/)&(https://hcs.kas.us/)&(https://si.kas.us/)", -// List.of(new KeySplitStep(KAS_UK, "1"), new KeySplitStep(KAS_US, "1"), -// new KeySplitStep(KAS_US_HCS, "2"), new KeySplitStep(KAS_US_SA, "3")))); -// -// for (ReasonerTestCase tc : testCases) { -// Granter reasoner = Autoconfigure.newGranterFromAttributes( -// valuesToPolicy(tc.getPolicy().toArray(new AttributeValueFQN[0])).toArray(new Value[0])); -// assertThat(reasoner).isNotNull(); -// -// AttributeBooleanExpression actualAB = reasoner.constructAttributeBoolean(); -// assertThat(actualAB.toString().toLowerCase()).isEqualTo(tc.getAts().toLowerCase()); -// -// BooleanKeyExpression actualKeyed = reasoner.insertKeysForAttribute(actualAB); -// assertThat(actualKeyed.toString()).isEqualTo(tc.getKeyed()); -// -// String reduced = actualKeyed.reduce().toString(); -// assertThat(reduced).isEqualTo(tc.getReduced()); -// -// var wrapper = new Object() { -// int i = 0; -// }; -// List plan = reasoner.plan(tc.getDefaults(), () -> { -// return String.valueOf(wrapper.i++ + 1); -// } -// -// ); -// assertThat(plan).isEqualTo(tc.getPlan()); -// } -// } -// -// GetAttributeValuesByFqnsResponse getResponse(GetAttributeValuesByFqnsRequest req) { -// GetAttributeValuesByFqnsResponse.Builder builder = GetAttributeValuesByFqnsResponse.newBuilder(); -// -// for (String v : req.getFqnsList()) { -// AttributeValueFQN vfqn; -// try { -// vfqn = new AttributeValueFQN(v); -// } catch (Exception e) { -// return null; // Or throw the exception as needed -// } -// -// Value val = mockValueFor(vfqn); -// -// builder.putFqnAttributeValues(v, GetAttributeValuesByFqnsResponse.AttributeAndValue.newBuilder() -// .setAttribute(val.getAttribute()) -// .setValue(val) -// .build()); -// } -// -// return builder.build(); -// } -// -// @Test -// public void testReasonerSpecificity() { -// List testCases = List.of( -// new ReasonerTestCase( -// "uns.uns => default", -// List.of(uns2uns), -// List.of(KAS_US), -// List.of(new KeySplitStep(KAS_US, ""))), -// new ReasonerTestCase( -// "uns.spk => spk", -// List.of(uns2spk), -// List.of(KAS_US), -// List.of(new KeySplitStep(EVEN_MORE_SPECIFIC_KAS, ""))), -// new ReasonerTestCase( -// "spk.uns => spk", -// List.of(spk2uns), -// List.of(KAS_US), -// List.of(new KeySplitStep(SPECIFIED_KAS, ""))), -// new ReasonerTestCase( -// "spk.spk => value.spk", -// List.of(spk2spk), -// List.of(KAS_US), -// List.of(new KeySplitStep(EVEN_MORE_SPECIFIC_KAS, ""))), -// new ReasonerTestCase( -// "spk.spk & spk.uns => value.spk || attr.spk", -// List.of(spk2spk, spk2uns), -// List.of(KAS_US), -// List.of(new KeySplitStep(EVEN_MORE_SPECIFIC_KAS, "1"), new KeySplitStep(SPECIFIED_KAS, "1"))), -// new ReasonerTestCase( -// "spk.uns & spk.spk => value.spk || attr.spk", -// List.of(spk2uns, spk2spk), -// List.of(KAS_US), -// List.of(new KeySplitStep(SPECIFIED_KAS, "1"), new KeySplitStep(EVEN_MORE_SPECIFIC_KAS, "1"))), -// new ReasonerTestCase( -// "uns.spk & spk.spk => value.spk", -// List.of(spk2spk, uns2spk), -// List.of(KAS_US), -// List.of(new KeySplitStep(EVEN_MORE_SPECIFIC_KAS, ""))), -// new ReasonerTestCase( -// "uns.spk & uns.uns => spk", -// List.of(uns2spk, uns2uns), -// List.of(KAS_US), -// List.of(new KeySplitStep(EVEN_MORE_SPECIFIC_KAS, ""))), -// new ReasonerTestCase( -// "uns.uns & uns.spk => spk", -// List.of(uns2uns, uns2spk), -// List.of(KAS_US), -// List.of(new KeySplitStep(EVEN_MORE_SPECIFIC_KAS, ""))), -// new ReasonerTestCase( -// "uns.uns & uns.spk => spk", -// List.of(uns2uns, spk2spk), -// List.of(KAS_US), -// List.of(new KeySplitStep(EVEN_MORE_SPECIFIC_KAS, ""))), -// new ReasonerTestCase( -// "spk.uns.uns => ns.spk", -// List.of(spk2uns2uns, uns2uns), -// List.of(KAS_US), -// List.of(new KeySplitStep(NAMESPACE_KAS, ""))), -// new ReasonerTestCase( -// "spk.uns.uns & uns.uns => ns.spk", -// List.of(spk2uns2uns, uns2uns), -// List.of(KAS_US), -// List.of(new KeySplitStep(NAMESPACE_KAS, ""))), -// new ReasonerTestCase( -// "spk.uns.uns & uns.spk => ns.spk && spk", -// List.of(spk2uns2uns, uns2spk), -// List.of(KAS_US), -// List.of(new KeySplitStep(NAMESPACE_KAS, "1"), new KeySplitStep(EVEN_MORE_SPECIFIC_KAS, "2"))), -// new ReasonerTestCase( -// "spk.uns.uns & spk.spk.uns && spk.uns.spk => ns.spk || attr.spk || value.spk", -// List.of(spk2uns2uns, spk2spk2uns, spk2uns2spk), -// List.of(KAS_US), -// List.of(new KeySplitStep(NAMESPACE_KAS, "1"), new KeySplitStep(EVEN_MORE_SPECIFIC_KAS, "1"), new KeySplitStep(SPECIFIED_KAS, "2"))) -// ); -// -// for (ReasonerTestCase tc : testCases) { -// assertDoesNotThrow(() -> { +package io.opentdf.platform.sdk; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.connectrpc.ResponseMessage; +import com.connectrpc.UnaryBlockingCall; +import io.opentdf.platform.policy.Attribute; +import io.opentdf.platform.policy.Value; +import io.opentdf.platform.policy.attributes.AttributesServiceClient; +import io.opentdf.platform.policy.attributes.AttributesServiceGrpc; +import io.opentdf.platform.policy.attributes.GetAttributeValuesByFqnsRequest; +import io.opentdf.platform.policy.attributes.GetAttributeValuesByFqnsResponse; +import io.opentdf.platform.sdk.Autoconfigure.AttributeValueFQN; +import io.opentdf.platform.sdk.Autoconfigure.Granter.AttributeBooleanExpression; +import io.opentdf.platform.sdk.Autoconfigure.Granter.BooleanKeyExpression; +import io.opentdf.platform.sdk.Autoconfigure.KeySplitStep; +import io.opentdf.platform.sdk.Autoconfigure.Granter; +import io.opentdf.platform.policy.Namespace; +import io.opentdf.platform.policy.PublicKey; +import io.opentdf.platform.policy.KeyAccessServer; +import io.opentdf.platform.policy.AttributeRuleTypeEnum; +import io.opentdf.platform.policy.KasPublicKey; +import io.opentdf.platform.policy.KasPublicKeyAlgEnum; +import io.opentdf.platform.policy.KasPublicKeySet; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import com.google.common.util.concurrent.SettableFuture; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.stream.Collectors; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class AutoconfigureTest { + + private static final String KAS_AU = "https://kas.au/"; + private static final String KAS_CA = "https://kas.ca/"; + private static final String KAS_UK = "https://kas.uk/"; + private static final String KAS_NZ = "https://kas.nz/"; + private static final String KAS_US = "https://kas.us/"; + private static final String KAS_US_HCS = "https://hcs.kas.us/"; + private static final String KAS_US_SA = "https://si.kas.us/"; + public static final String SPECIFIED_KAS = "https://attr.kas.com/"; + public static final String EVEN_MORE_SPECIFIC_KAS = "https://value.kas.com/"; + private static final String NAMESPACE_KAS = "https://namespace.kas.com/"; + private static Autoconfigure.AttributeNameFQN SPKSPECKED; + private static Autoconfigure.AttributeNameFQN SPKUNSPECKED; + + private static Autoconfigure.AttributeNameFQN CLS; + private static Autoconfigure.AttributeNameFQN N2K; + private static Autoconfigure.AttributeNameFQN REL; + private static Autoconfigure.AttributeNameFQN UNSPECKED; + private static Autoconfigure.AttributeNameFQN SPECKED; + + private static Autoconfigure.AttributeValueFQN clsA; + private static Autoconfigure.AttributeValueFQN clsS; + private static Autoconfigure.AttributeValueFQN clsTS; + private static Autoconfigure.AttributeValueFQN n2kHCS; + private static Autoconfigure.AttributeValueFQN n2kInt; + private static Autoconfigure.AttributeValueFQN n2kSI; + private static Autoconfigure.AttributeValueFQN rel2can; + private static Autoconfigure.AttributeValueFQN rel2gbr; + private static Autoconfigure.AttributeValueFQN rel2nzl; + private static Autoconfigure.AttributeValueFQN rel2usa; + private static Autoconfigure.AttributeValueFQN uns2uns; + private static Autoconfigure.AttributeValueFQN uns2spk; + private static Autoconfigure.AttributeValueFQN spk2uns; + private static Autoconfigure.AttributeValueFQN spk2spk; + private static AttributeValueFQN spk2uns2uns; + private static AttributeValueFQN spk2uns2spk; + private static Autoconfigure.AttributeValueFQN spk2spk2uns; + private static Autoconfigure.AttributeValueFQN spk2spk2spk; + + @BeforeAll + public static void setup() throws AutoConfigureException { + // Initialize the FQNs (Fully Qualified Names) + CLS = new Autoconfigure.AttributeNameFQN("https://virtru.com/attr/Classification"); + N2K = new Autoconfigure.AttributeNameFQN("https://virtru.com/attr/Need%20to%20Know"); + REL = new Autoconfigure.AttributeNameFQN("https://virtru.com/attr/Releasable%20To"); + UNSPECKED = new Autoconfigure.AttributeNameFQN("https://other.com/attr/unspecified"); + SPECKED = new Autoconfigure.AttributeNameFQN("https://other.com/attr/specified"); + SPKUNSPECKED = new Autoconfigure.AttributeNameFQN("https://hasgrants.com/attr/unspecified"); + SPKSPECKED = new Autoconfigure.AttributeNameFQN("https://hasgrants.com/attr/specified"); + + clsA = new Autoconfigure.AttributeValueFQN("https://virtru.com/attr/Classification/value/Allowed"); + clsS = new Autoconfigure.AttributeValueFQN("https://virtru.com/attr/Classification/value/Secret"); + clsTS = new Autoconfigure.AttributeValueFQN("https://virtru.com/attr/Classification/value/Top%20Secret"); + + n2kHCS = new Autoconfigure.AttributeValueFQN("https://virtru.com/attr/Need%20to%20Know/value/HCS"); + n2kInt = new Autoconfigure.AttributeValueFQN("https://virtru.com/attr/Need%20to%20Know/value/INT"); + n2kSI = new Autoconfigure.AttributeValueFQN("https://virtru.com/attr/Need%20to%20Know/value/SI"); + + rel2can = new Autoconfigure.AttributeValueFQN("https://virtru.com/attr/Releasable%20To/value/CAN"); + rel2gbr = new Autoconfigure.AttributeValueFQN("https://virtru.com/attr/Releasable%20To/value/GBR"); + rel2nzl = new Autoconfigure.AttributeValueFQN("https://virtru.com/attr/Releasable%20To/value/NZL"); + rel2usa = new Autoconfigure.AttributeValueFQN("https://virtru.com/attr/Releasable%20To/value/USA"); + + uns2uns = new Autoconfigure.AttributeValueFQN("https://other.com/attr/unspecified/value/unspecked"); + uns2spk = new Autoconfigure.AttributeValueFQN("https://other.com/attr/unspecified/value/specked"); + spk2uns = new Autoconfigure.AttributeValueFQN("https://other.com/attr/specified/value/unspecked"); + spk2spk = new Autoconfigure.AttributeValueFQN("https://other.com/attr/specified/value/specked"); + + spk2uns2uns = new Autoconfigure.AttributeValueFQN("https://hasgrants.com/attr/unspecified/value/unspecked"); + spk2uns2spk = new Autoconfigure.AttributeValueFQN("https://hasgrants.com/attr/unspecified/value/specked"); + spk2spk2uns = new Autoconfigure.AttributeValueFQN("https://hasgrants.com/attr/specified/value/unspecked"); + spk2spk2spk = new Autoconfigure.AttributeValueFQN("https://hasgrants.com/attr/specified/value/specked"); + } + + private static String spongeCase(String s) { + final Pattern PATTERN = Pattern.compile("^(https?://[\\w./]+/attr/)([^/]*)(/value/)?(\\S*)?$"); + Matcher matcher = PATTERN.matcher(s); + + if (!matcher.matches()) { + throw new IllegalArgumentException("Invalid input string"); + } + + StringBuilder sb = new StringBuilder(); + sb.append(matcher.group(1)); + + String n = matcher.group(2); + for (int i = 0; i < n.length(); i++) { + String sub = n.substring(i, i + 1); + if ((i & 1) == 1) { + sb.append(sub.toUpperCase()); + } else { + sb.append(sub); + } + } + + if (matcher.group(3) != null) { + sb.append(matcher.group(3)); + String v = matcher.group(4); + for (int i = 0; i < v.length(); i++) { + String sub = v.substring(i, i + 1); + if ((i & 1) == 1) { + sb.append(sub); + } else { + sb.append(sub.toUpperCase()); + } + } + } + return sb.toString(); + } + + private List valuesToPolicy(AttributeValueFQN... p) throws AutoConfigureException { + List values = new ArrayList<>(); + for (AttributeValueFQN afqn : List.of(p)) { + values.add(mockValueFor(afqn)); + } + return values; + } + + private List policyToStringKeys(List policy) { + return policy.stream() + .map(AttributeValueFQN::getKey) + .collect(Collectors.toList()); + } + + private Autoconfigure.AttributeValueFQN messUpV(Autoconfigure.AttributeValueFQN a) { + try { + return new Autoconfigure.AttributeValueFQN(spongeCase(a.toString())); + } catch (Exception e) { + throw new RuntimeException("Failed to create AttributeValueFQN", e); + } + } + + private Attribute mockAttributeFor(Autoconfigure.AttributeNameFQN fqn) { + Namespace ns1 = Namespace.newBuilder().setId("v").setName("virtru.com").setFqn("https://virtru.com").build(); + Namespace ns2 = Namespace.newBuilder().setId("o").setName("other.com").setFqn("https://other.com").build(); + Namespace ns3 = Namespace.newBuilder().setId("h").setName("hasgrants.com").addGrants(KeyAccessServer.newBuilder().setUri(NAMESPACE_KAS).build()).setFqn("https://hasgrants.com").build(); + + String key = fqn.getKey(); + if (key.equals(CLS.getKey())) { + return Attribute.newBuilder().setId("CLS").setNamespace(ns1) + .setName("Classification").setRule(AttributeRuleTypeEnum.ATTRIBUTE_RULE_TYPE_ENUM_HIERARCHY) + .setFqn(fqn.toString()).build(); + } else if (key.equals(N2K.getKey())) { + return Attribute.newBuilder().setId("N2K").setNamespace(ns1) + .setName("Need to Know").setRule(AttributeRuleTypeEnum.ATTRIBUTE_RULE_TYPE_ENUM_ALL_OF) + .setFqn(fqn.toString()).build(); + } else if (key.equals(REL.getKey())) { + return Attribute.newBuilder().setId("REL").setNamespace(ns1) + .setName("Releasable To").setRule(AttributeRuleTypeEnum.ATTRIBUTE_RULE_TYPE_ENUM_ANY_OF) + .setFqn(fqn.toString()).build(); + } else if (key.equals(SPECKED.getKey())) { + return Attribute.newBuilder().setId("SPK").setNamespace(ns2) + .setName("specified").setRule(AttributeRuleTypeEnum.ATTRIBUTE_RULE_TYPE_ENUM_ANY_OF) + .setFqn(fqn.toString()) + .addGrants(KeyAccessServer.newBuilder().setUri(SPECIFIED_KAS).build()) + .build(); + } else if (key.equals(UNSPECKED.getKey())) { + return Attribute.newBuilder().setId("UNS").setNamespace(ns2) + .setName("unspecified").setRule(AttributeRuleTypeEnum.ATTRIBUTE_RULE_TYPE_ENUM_ANY_OF) + .setFqn(fqn.toString()).build(); + } else if (key.equals(SPKSPECKED.getKey())) { + return Attribute.newBuilder().setId("SPKSPK").setNamespace(ns3) + .setName("specified").setRule(AttributeRuleTypeEnum.ATTRIBUTE_RULE_TYPE_ENUM_ANY_OF) + .addGrants(KeyAccessServer.newBuilder().setUri(SPECIFIED_KAS).build()) + .setName(fqn.toString()) + .build(); + } else if (key.equals(SPKUNSPECKED.getKey())) { + return Attribute.newBuilder().setId("SPKUNSPK").setNamespace(ns3) + .setName("unspecified").setRule(AttributeRuleTypeEnum.ATTRIBUTE_RULE_TYPE_ENUM_ANY_OF) + .setName(fqn.toString()) + .build(); + } + + throw new IllegalArgumentException("Key not recognized: " + key); + } + + private Value mockValueFor(Autoconfigure.AttributeValueFQN fqn) throws AutoConfigureException { + Autoconfigure.AttributeNameFQN an = fqn.prefix(); + Attribute a = mockAttributeFor(an); + String v = fqn.value(); + Value p = Value.newBuilder() + .setId(a.getId() + ":" + v) + .setAttribute(a) + .setValue(v) + .setFqn(fqn.toString()) + .build(); + + if (an.getKey().equals(N2K.getKey())) { + switch (v.toUpperCase()) { + case "INT": + p = p.toBuilder().addGrants(KeyAccessServer.newBuilder().setUri(KAS_UK).build()).build(); + break; + case "HCS": + p = p.toBuilder().addGrants(KeyAccessServer.newBuilder().setUri(KAS_US_HCS).build()).build(); + break; + case "SI": + p = p.toBuilder().addGrants(KeyAccessServer.newBuilder().setUri(KAS_US_SA).build()).build(); + break; + } + } else if (an.getKey().equals(REL.getKey())) { + switch (v.toUpperCase()) { + case "FVEY": + p = p.toBuilder().addGrants(KeyAccessServer.newBuilder().setUri(KAS_AU).build()) + .addGrants(1, KeyAccessServer.newBuilder().setUri(KAS_CA).build()) + .addGrants(1, KeyAccessServer.newBuilder().setUri(KAS_UK).build()) + .addGrants(1, KeyAccessServer.newBuilder().setUri(KAS_NZ).build()) + .addGrants(1, KeyAccessServer.newBuilder().setUri(KAS_US).build()) + .build(); + break; + case "AUS": + p = p.toBuilder().addGrants(KeyAccessServer.newBuilder().setUri(KAS_AU).build()) + .build(); + break; + case "CAN": + p = p.toBuilder().addGrants(0, KeyAccessServer.newBuilder().setUri(KAS_CA).build()) + .build(); + break; + case "GBR": + p = p.toBuilder().addGrants(KeyAccessServer.newBuilder().setUri(KAS_UK).build()) + .build(); + break; + case "NZL": + p = p.toBuilder().addGrants(KeyAccessServer.newBuilder().setUri(KAS_NZ).build()) + .build(); + break; + case "USA": + p = p.toBuilder().addGrants(KeyAccessServer.newBuilder().setUri(KAS_US).build()) + .build(); + break; + } + } else if (an.getKey().equals(CLS.getKey())) { + // defaults only + } else if (List.of(SPECKED.getKey(), SPKSPECKED.getKey()).contains(an.getKey())) { + if (fqn.value().equalsIgnoreCase("specked")) { + p = p.toBuilder().addGrants(KeyAccessServer.newBuilder().setUri(EVEN_MORE_SPECIFIC_KAS).build()) + .build(); + } + } else if (List.of(UNSPECKED.getKey(), SPKUNSPECKED.getKey()).contains(an.getKey())) { + if (fqn.value().equalsIgnoreCase("specked")) { + p = p.toBuilder().addGrants(KeyAccessServer.newBuilder().setUri(EVEN_MORE_SPECIFIC_KAS).build()) + .build(); + } + } + return p; + } + + @Test + public void testAttributeFromURL() throws AutoConfigureException { + for (TestCase tc : List.of( + new TestCase("letter", "https://e/attr/a", "https://e", "a"), + new TestCase("number", "https://e/attr/1", "https://e", "1"), + new TestCase("emoji", "https://e/attr/%F0%9F%98%81", "https://e", "😁"), + new TestCase("dash", "https://a-b.com/attr/b-c", "https://a-b.com", "b-c"))) { + Autoconfigure.AttributeNameFQN a = new Autoconfigure.AttributeNameFQN(tc.getU()); + assertThat(a.authority()).isEqualTo(tc.getAuth()); + assertThat(a.name()).isEqualTo(tc.getName()); + } + } + + @Test + public void testAttributeFromMalformedURL() { + for (TestCase tc : List.of( + new TestCase("no name", "https://e/attr"), + new TestCase("invalid prefix 1", "hxxp://e/attr/a"), + new TestCase("invalid prefix 2", "e/attr/a"), + new TestCase("invalid prefix 3", "file://e/attr/a"), + new TestCase("invalid prefix 4", "https:///attr/a"), + new TestCase("bad encoding", "https://a/attr/%😁"), + new TestCase("with value", "https://e/attr/a/value/b"))) { + assertThatThrownBy(() -> new Autoconfigure.AttributeNameFQN(tc.getU())) + .isInstanceOf(AutoConfigureException.class); + } + } + + @Test + public void testAttributeValueFromURL() { + List testCases = List.of( + new TestCase("number", "https://e/attr/a/value/1", "https://e", "a", "1"), + new TestCase("space", "https://e/attr/a/value/%20", "https://e", "a", " "), + new TestCase("emoji", "https://e/attr/a/value/%F0%9F%98%81", "https://e", "a", "😁"), + new TestCase("numberdef", "https://e/attr/1/value/one", "https://e", "1", "one"), + new TestCase("valuevalue", "https://e/attr/value/value/one", "https://e", "value", "one"), + new TestCase("dash", "https://a-b.com/attr/b-c/value/c-d", "https://a-b.com", "b-c", "c-d")); + + for (TestCase tc : testCases) { + assertDoesNotThrow(() -> { + AttributeValueFQN a = new AttributeValueFQN(tc.getU()); + assertThat(a.authority()).isEqualTo(tc.getAuth()); + assertThat(a.name()).isEqualTo(tc.getName()); + assertThat(a.value()).isEqualTo(tc.getValue()); + }); + } + } + + @Test + public void testAttributeValueFromMalformedURL() { + List testCases = List.of( + new TestCase("no name", "https://e/attr/value/1"), + new TestCase("no value", "https://e/attr/who/value"), + new TestCase("invalid prefix 1", "hxxp://e/attr/a/value/1"), + new TestCase("invalid prefix 2", "e/attr/a/a/value/1"), + new TestCase("bad encoding", "https://a/attr/emoji/value/%😁")); + + for (TestCase tc : testCases) { + assertThatThrownBy(() -> new AttributeValueFQN(tc.getU())) + .isInstanceOf(AutoConfigureException.class) + .hasMessageContaining("invalid type"); + } + } + + @Test + public void testConfigurationServicePutGet() { + List testCases = List.of( + new ConfigurationTestCase("default", List.of(clsA), 1, List.of()), + new ConfigurationTestCase("one-country", List.of(rel2gbr), 1, List.of(KAS_UK)), + new ConfigurationTestCase("two-country", List.of(rel2gbr, rel2nzl), 2, List.of(KAS_UK, KAS_NZ)), + new ConfigurationTestCase("with-default", List.of(clsA, rel2gbr), 2, List.of(KAS_UK)), + new ConfigurationTestCase("need-to-know", List.of(clsTS, rel2usa, n2kSI), 3, + List.of(KAS_US, KAS_US_SA))); + + for (ConfigurationTestCase tc : testCases) { + assertDoesNotThrow(() -> { + List v = valuesToPolicy(tc.getPolicy().toArray(new AttributeValueFQN[0])); + Granter grants = Autoconfigure.newGranterFromAttributes(v.toArray(new Value[0])); + assertThat(grants).isNotNull(); + assertThat(grants.getGrants()).hasSize(tc.getSize()); + assertThat(policyToStringKeys(tc.getPolicy())).containsAll(grants.getGrants().keySet()); + Set actualKases = new HashSet<>(); + for (Autoconfigure.KeyAccessGrant g : grants.getGrants().values()) { + assertThat(g).isNotNull(); + for (String k : g.kases) { + actualKases.add(k); + } + } + + String[] kasArray = tc.getKases().toArray(new String[tc.getKases().size()]); + assertThat(actualKases).containsExactlyInAnyOrder(kasArray); + }); + } + } + + @Test + public void testReasonerConstructAttributeBoolean() { + List testCases = List.of( + new ReasonerTestCase( + "one actual with default", + List.of(clsS, rel2can), + List.of(KAS_US), + "https://virtru.com/attr/Classification/value/Secret&https://virtru.com/attr/Releasable%20To/value/CAN", + "[DEFAULT]&(https://kas.ca/)", + "(https://kas.ca/)", + List.of(new KeySplitStep(KAS_CA, ""))), + new ReasonerTestCase( + "one defaulted attr", + List.of(clsS), + List.of(KAS_US), + "https://virtru.com/attr/Classification/value/Secret", + "[DEFAULT]", + "", + List.of(new KeySplitStep(KAS_US, ""))), + new ReasonerTestCase( + "empty policy", + List.of(), + List.of(KAS_US), + "∅", + "", + "", + List.of(new KeySplitStep(KAS_US, ""))), + new ReasonerTestCase( + "old school splits", + List.of(), + List.of(KAS_AU, KAS_CA, KAS_US), + "∅", + "", + "", + List.of(new KeySplitStep(KAS_AU, "1"), new KeySplitStep(KAS_CA, "2"), + new KeySplitStep(KAS_US, "3"))), + new ReasonerTestCase( + "simple with all three ops", + List.of(clsS, rel2gbr, n2kInt), + List.of(KAS_US), + "https://virtru.com/attr/Classification/value/Secret&https://virtru.com/attr/Releasable%20To/value/GBR&https://virtru.com/attr/Need%20to%20Know/value/INT", + "[DEFAULT]&(https://kas.uk/)&(https://kas.uk/)", + "(https://kas.uk/)", + List.of(new KeySplitStep(KAS_UK, ""))), + new ReasonerTestCase( + "compartments", + List.of(clsS, rel2gbr, rel2usa, n2kHCS, n2kSI), + List.of(KAS_US), + "https://virtru.com/attr/Classification/value/Secret&https://virtru.com/attr/Releasable%20To/value/{GBR,USA}&https://virtru.com/attr/Need%20to%20Know/value/{HCS,SI}", + "[DEFAULT]&(https://kas.uk/⋁https://kas.us/)&(https://hcs.kas.us/⋀https://si.kas.us/)", + "(https://kas.uk/⋁https://kas.us/)&(https://hcs.kas.us/)&(https://si.kas.us/)", + List.of(new KeySplitStep(KAS_UK, "1"), new KeySplitStep(KAS_US, "1"), + new KeySplitStep(KAS_US_HCS, "2"), new KeySplitStep(KAS_US_SA, "3"))), + new ReasonerTestCase( + "compartments - case insensitive", + List.of( + messUpV(clsS), messUpV(rel2gbr), messUpV(rel2usa), messUpV(n2kHCS), messUpV(n2kSI)), + List.of(KAS_US), + "https://virtru.com/attr/Classification/value/Secret&https://virtru.com/attr/Releasable%20To/value/{GBR,USA}&https://virtru.com/attr/Need%20to%20Know/value/{HCS,SI}", + "[DEFAULT]&(https://kas.uk/⋁https://kas.us/)&(https://hcs.kas.us/⋀https://si.kas.us/)", + "(https://kas.uk/⋁https://kas.us/)&(https://hcs.kas.us/)&(https://si.kas.us/)", + List.of(new KeySplitStep(KAS_UK, "1"), new KeySplitStep(KAS_US, "1"), + new KeySplitStep(KAS_US_HCS, "2"), new KeySplitStep(KAS_US_SA, "3")))); + + for (ReasonerTestCase tc : testCases) { + Granter reasoner = Autoconfigure.newGranterFromAttributes( + valuesToPolicy(tc.getPolicy().toArray(new AttributeValueFQN[0])).toArray(new Value[0])); + assertThat(reasoner).isNotNull(); + + AttributeBooleanExpression actualAB = reasoner.constructAttributeBoolean(); + assertThat(actualAB.toString().toLowerCase()).isEqualTo(tc.getAts().toLowerCase()); + + BooleanKeyExpression actualKeyed = reasoner.insertKeysForAttribute(actualAB); + assertThat(actualKeyed.toString()).isEqualTo(tc.getKeyed()); + + String reduced = actualKeyed.reduce().toString(); + assertThat(reduced).isEqualTo(tc.getReduced()); + + var wrapper = new Object() { + int i = 0; + }; + List plan = reasoner.plan(tc.getDefaults(), () -> { + return String.valueOf(wrapper.i++ + 1); + } + + ); + assertThat(plan).isEqualTo(tc.getPlan()); + } + } + + GetAttributeValuesByFqnsResponse getResponse(GetAttributeValuesByFqnsRequest req) { + GetAttributeValuesByFqnsResponse.Builder builder = GetAttributeValuesByFqnsResponse.newBuilder(); + + for (String v : req.getFqnsList()) { + AttributeValueFQN vfqn; + try { + vfqn = new AttributeValueFQN(v); + } catch (Exception e) { + return null; // Or throw the exception as needed + } + + Value val = mockValueFor(vfqn); + + builder.putFqnAttributeValues(v, GetAttributeValuesByFqnsResponse.AttributeAndValue.newBuilder() + .setAttribute(val.getAttribute()) + .setValue(val) + .build()); + } + + return builder.build(); + } + + @Test + public void testReasonerSpecificity() { + List testCases = List.of( + new ReasonerTestCase( + "uns.uns => default", + List.of(uns2uns), + List.of(KAS_US), + List.of(new KeySplitStep(KAS_US, ""))), + new ReasonerTestCase( + "uns.spk => spk", + List.of(uns2spk), + List.of(KAS_US), + List.of(new KeySplitStep(EVEN_MORE_SPECIFIC_KAS, ""))), + new ReasonerTestCase( + "spk.uns => spk", + List.of(spk2uns), + List.of(KAS_US), + List.of(new KeySplitStep(SPECIFIED_KAS, ""))), + new ReasonerTestCase( + "spk.spk => value.spk", + List.of(spk2spk), + List.of(KAS_US), + List.of(new KeySplitStep(EVEN_MORE_SPECIFIC_KAS, ""))), + new ReasonerTestCase( + "spk.spk & spk.uns => value.spk || attr.spk", + List.of(spk2spk, spk2uns), + List.of(KAS_US), + List.of(new KeySplitStep(EVEN_MORE_SPECIFIC_KAS, "1"), new KeySplitStep(SPECIFIED_KAS, "1"))), + new ReasonerTestCase( + "spk.uns & spk.spk => value.spk || attr.spk", + List.of(spk2uns, spk2spk), + List.of(KAS_US), + List.of(new KeySplitStep(SPECIFIED_KAS, "1"), new KeySplitStep(EVEN_MORE_SPECIFIC_KAS, "1"))), + new ReasonerTestCase( + "uns.spk & spk.spk => value.spk", + List.of(spk2spk, uns2spk), + List.of(KAS_US), + List.of(new KeySplitStep(EVEN_MORE_SPECIFIC_KAS, ""))), + new ReasonerTestCase( + "uns.spk & uns.uns => spk", + List.of(uns2spk, uns2uns), + List.of(KAS_US), + List.of(new KeySplitStep(EVEN_MORE_SPECIFIC_KAS, ""))), + new ReasonerTestCase( + "uns.uns & uns.spk => spk", + List.of(uns2uns, uns2spk), + List.of(KAS_US), + List.of(new KeySplitStep(EVEN_MORE_SPECIFIC_KAS, ""))), + new ReasonerTestCase( + "uns.uns & uns.spk => spk", + List.of(uns2uns, spk2spk), + List.of(KAS_US), + List.of(new KeySplitStep(EVEN_MORE_SPECIFIC_KAS, ""))), + new ReasonerTestCase( + "spk.uns.uns => ns.spk", + List.of(spk2uns2uns, uns2uns), + List.of(KAS_US), + List.of(new KeySplitStep(NAMESPACE_KAS, ""))), + new ReasonerTestCase( + "spk.uns.uns & uns.uns => ns.spk", + List.of(spk2uns2uns, uns2uns), + List.of(KAS_US), + List.of(new KeySplitStep(NAMESPACE_KAS, ""))), + new ReasonerTestCase( + "spk.uns.uns & uns.spk => ns.spk && spk", + List.of(spk2uns2uns, uns2spk), + List.of(KAS_US), + List.of(new KeySplitStep(NAMESPACE_KAS, "1"), new KeySplitStep(EVEN_MORE_SPECIFIC_KAS, "2"))), + new ReasonerTestCase( + "spk.uns.uns & spk.spk.uns && spk.uns.spk => ns.spk || attr.spk || value.spk", + List.of(spk2uns2uns, spk2spk2uns, spk2uns2spk), + List.of(KAS_US), + List.of(new KeySplitStep(NAMESPACE_KAS, "1"), new KeySplitStep(EVEN_MORE_SPECIFIC_KAS, "1"), new KeySplitStep(SPECIFIED_KAS, "2"))) + ); + + for (ReasonerTestCase tc : testCases) { + var attributeService = mock(AttributesServiceClient.class); + when(attributeService.getAttributeValuesByFqnsBlocking(any(), any())).thenReturn( + new UnaryBlockingCall<>() { + @Override + public ResponseMessage execute() { + return new ResponseMessage.Success<>( + getResponse(GetAttributeValuesByFqnsRequest.newBuilder() + .addAllFqns(tc.getPolicy().stream().map(AttributeValueFQN::toString).collect(Collectors.toList())) + .build()), Collections.emptyMap(), Collections.emptyMap()); + } + + @Override + public void cancel() { + } + } + ); + // AttributesServiceGrpc.AttributesServiceFutureStub attributeGrpcStub = mock( // AttributesServiceGrpc.AttributesServiceFutureStub.class); // lenient().when(attributeGrpcStub.getAttributeValuesByFqns(any(GetAttributeValuesByFqnsRequest.class))) @@ -587,248 +607,342 @@ // future.set(resp); // Set the request as the future's result // return future; // }); -// -// Granter reasoner = Autoconfigure.newGranterFromService(attributeGrpcStub, new KASKeyCache(), -// tc.getPolicy().toArray(new AttributeValueFQN[0])); -// assertThat(reasoner).isNotNull(); -// -// var wrapper = new Object() { -// int i = 0; -// }; -// List plan = reasoner.plan(tc.getDefaults(), () -> { -// return String.valueOf(wrapper.i++ + 1); -// } -// -// ); -// assertThat(plan).hasSameElementsAs(tc.getPlan()); -// }); -// } -// } -// -// private static class TestCase { -// private final String n; -// private final String u; -// private final String auth; -// private final String name; -// private final String value; -// -// TestCase(String n, String u, String auth, String name, String value) { -// this.n = n; -// this.u = u; -// this.auth = auth; -// this.name = name; -// this.value = value; -// } -// -// TestCase(String n, String u, String auth, String name) { -// this(n, u, auth, name, null); -// } -// -// TestCase(String n, String u) { -// this(n, u, null, null, null); -// } -// -// public String getU() { -// return u; -// } -// -// public String getAuth() { -// return auth; -// } -// -// public String getName() { -// return name; -// } -// -// public String getValue() { -// return value; -// } -// -// public Autoconfigure.AttributeValueFQN getA() throws AutoConfigureException { -// return new Autoconfigure.AttributeValueFQN(u); -// } -// } -// -// private static class ConfigurationTestCase { -// private final String name; -// private final List policy; -// private final int size; -// private final List kases; -// -// ConfigurationTestCase(String name, List policy, int size, List kases) { -// this.name = name; -// this.policy = policy; -// this.size = size; -// this.kases = kases; -// } -// -// public String getN() { -// return name; -// } -// -// public List getPolicy() { -// return policy; -// } -// -// public int getSize() { -// return size; -// } -// -// public List getKases() { -// return kases; -// } -// } -// -// private static class ReasonerTestCase { -// private final String name; -// private final List policy; -// private final List defaults; -// private final String ats; -// private final String keyed; -// private final String reduced; -// private final List plan; -// -// ReasonerTestCase(String name, List policy, List defaults, String ats, String keyed, -// String reduced, List plan) { -// this.name = name; -// this.policy = policy; -// this.defaults = defaults; -// this.ats = ats; -// this.keyed = keyed; -// this.reduced = reduced; -// this.plan = plan; -// } -// -// ReasonerTestCase(String name, List policy, List defaults, List plan) { -// this.name = name; -// this.policy = policy; -// this.defaults = defaults; -// this.plan = plan; -// this.ats = null; -// this.keyed = null; -// this.reduced = null; -// } -// -// public String getN() { -// return name; -// } -// -// public List getPolicy() { -// return policy; -// } -// -// public List getDefaults() { -// return defaults; -// } -// -// public String getAts() { -// return ats; -// } -// -// public String getKeyed() { -// return keyed; -// } -// -// public String getReduced() { -// return reduced; -// } -// -// public List getPlan() { -// return plan; -// } -// } -// -// @Test -// void testStoreKeysToCache_NoKeys() { -// KASKeyCache keyCache = Mockito.mock(KASKeyCache.class); -// KeyAccessServer kas1 = KeyAccessServer.newBuilder().setPublicKey( -// PublicKey.newBuilder().setCached( -// KasPublicKeySet.newBuilder())) -// .build(); -// -// List kases = List.of(kas1); -// -// Autoconfigure.storeKeysToCache(kases, keyCache); -// -// verify(keyCache, never()).store(any(Config.KASInfo.class)); -// } -// -// @Test -// void testStoreKeysToCache_WithKeys() { -// // Create a real KASKeyCache instance instead of mocking it -// KASKeyCache keyCache = new KASKeyCache(); -// -// // Create the KasPublicKey object -// KasPublicKey kasPublicKey1 = KasPublicKey.newBuilder() -// .setAlg(KasPublicKeyAlgEnum.KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP256R1) -// .setKid("test-kid") -// .setPem("public-key-pem") -// .build(); -// -// // Add the KasPublicKey to a list -// List kasPublicKeys = new ArrayList<>(); -// kasPublicKeys.add(kasPublicKey1); -// -// // Create the KeyAccessServer object -// KeyAccessServer kas1 = KeyAccessServer.newBuilder() -// .setPublicKey(PublicKey.newBuilder() -// .setCached(KasPublicKeySet.newBuilder() -// .addAllKeys(kasPublicKeys) -// .build())) -// .setUri("https://example.com/kas") -// .build(); -// -// // Add the KeyAccessServer to a list -// List kases = List.of(kas1); -// -// // Call the method under test -// Autoconfigure.storeKeysToCache(kases, keyCache); -// -// // Verify that the key was stored in the cache -// Config.KASInfo storedKASInfo = keyCache.get("https://example.com/kas", "ec:secp256r1"); -// assertNotNull(storedKASInfo); -// assertEquals("https://example.com/kas", storedKASInfo.URL); -// assertEquals("test-kid", storedKASInfo.KID); -// assertEquals("ec:secp256r1", storedKASInfo.Algorithm); -// assertEquals("public-key-pem", storedKASInfo.PublicKey); -// } -// -// @Test -// void testStoreKeysToCache_MultipleKasEntries() { -// // Create a real KASKeyCache instance instead of mocking it + + Granter reasoner = Autoconfigure.newGranterFromService(attributeService, new KASKeyCache(), + tc.getPolicy().toArray(new AttributeValueFQN[0])); + assertThat(reasoner).isNotNull(); + + var wrapper = new Object() { + int i = 0; + }; + List plan = reasoner.plan(tc.getDefaults(), () -> { + return String.valueOf(wrapper.i++ + 1); + } + + ); + assertThat(plan).hasSameElementsAs(tc.getPlan()); + } + } + + private static class TestCase { + private final String n; + private final String u; + private final String auth; + private final String name; + private final String value; + + TestCase(String n, String u, String auth, String name, String value) { + this.n = n; + this.u = u; + this.auth = auth; + this.name = name; + this.value = value; + } + + TestCase(String n, String u, String auth, String name) { + this(n, u, auth, name, null); + } + + TestCase(String n, String u) { + this(n, u, null, null, null); + } + + public String getU() { + return u; + } + + public String getAuth() { + return auth; + } + + public String getName() { + return name; + } + + public String getValue() { + return value; + } + + public Autoconfigure.AttributeValueFQN getA() throws AutoConfigureException { + return new Autoconfigure.AttributeValueFQN(u); + } + } + + private static class ConfigurationTestCase { + private final String name; + private final List policy; + private final int size; + private final List kases; + + ConfigurationTestCase(String name, List policy, int size, List kases) { + this.name = name; + this.policy = policy; + this.size = size; + this.kases = kases; + } + + public String getN() { + return name; + } + + public List getPolicy() { + return policy; + } + + public int getSize() { + return size; + } + + public List getKases() { + return kases; + } + } + + private static class ReasonerTestCase { + private final String name; + private final List policy; + private final List defaults; + private final String ats; + private final String keyed; + private final String reduced; + private final List plan; + + ReasonerTestCase(String name, List policy, List defaults, String ats, String keyed, + String reduced, List plan) { + this.name = name; + this.policy = policy; + this.defaults = defaults; + this.ats = ats; + this.keyed = keyed; + this.reduced = reduced; + this.plan = plan; + } + + ReasonerTestCase(String name, List policy, List defaults, List plan) { + this.name = name; + this.policy = policy; + this.defaults = defaults; + this.plan = plan; + this.ats = null; + this.keyed = null; + this.reduced = null; + } + + public String getN() { + return name; + } + + public List getPolicy() { + return policy; + } + + public List getDefaults() { + return defaults; + } + + public String getAts() { + return ats; + } + + public String getKeyed() { + return keyed; + } + + public String getReduced() { + return reduced; + } + + public List getPlan() { + return plan; + } + } + + @Test + void testStoreKeysToCache_NoKeys() { + KASKeyCache keyCache = Mockito.mock(KASKeyCache.class); + KeyAccessServer kas1 = KeyAccessServer.newBuilder().setPublicKey( + PublicKey.newBuilder().setCached( + KasPublicKeySet.newBuilder())) + .build(); + + List kases = List.of(kas1); + + Autoconfigure.storeKeysToCache(kases, keyCache); + + verify(keyCache, never()).store(any(Config.KASInfo.class)); + } + + @Test + void testStoreKeysToCache_WithKeys() { + // Create a real KASKeyCache instance instead of mocking it + KASKeyCache keyCache = new KASKeyCache(); + + // Create the KasPublicKey object + KasPublicKey kasPublicKey1 = KasPublicKey.newBuilder() + .setAlg(KasPublicKeyAlgEnum.KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP256R1) + .setKid("test-kid") + .setPem("public-key-pem") + .build(); + + // Add the KasPublicKey to a list + List kasPublicKeys = new ArrayList<>(); + kasPublicKeys.add(kasPublicKey1); + + // Create the KeyAccessServer object + KeyAccessServer kas1 = KeyAccessServer.newBuilder() + .setPublicKey(PublicKey.newBuilder() + .setCached(KasPublicKeySet.newBuilder() + .addAllKeys(kasPublicKeys) + .build())) + .setUri("https://example.com/kas") + .build(); + + // Add the KeyAccessServer to a list + List kases = List.of(kas1); + + // Call the method under test + Autoconfigure.storeKeysToCache(kases, keyCache); + + // Verify that the key was stored in the cache + Config.KASInfo storedKASInfo = keyCache.get("https://example.com/kas", "ec:secp256r1"); + assertNotNull(storedKASInfo); + assertEquals("https://example.com/kas", storedKASInfo.URL); + assertEquals("test-kid", storedKASInfo.KID); + assertEquals("ec:secp256r1", storedKASInfo.Algorithm); + assertEquals("public-key-pem", storedKASInfo.PublicKey); + } + + @Test + void testStoreKeysToCache_MultipleKasEntries() { + // Create a real KASKeyCache instance instead of mocking it + KASKeyCache keyCache = new KASKeyCache(); + + // Create the KasPublicKey object + KasPublicKey kasPublicKey1 = KasPublicKey.newBuilder() + .setAlg(KasPublicKeyAlgEnum.KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP256R1) + .setKid("test-kid") + .setPem("public-key-pem") + .build(); + KasPublicKey kasPublicKey2 = KasPublicKey.newBuilder() + .setAlg(KasPublicKeyAlgEnum.KAS_PUBLIC_KEY_ALG_ENUM_RSA_2048) + .setKid("test-kid-2") + .setPem("public-key-pem-2") + .build(); + + // Add the KasPublicKey to a list + List kasPublicKeys = new ArrayList<>(); + kasPublicKeys.add(kasPublicKey1); + kasPublicKeys.add(kasPublicKey2); + + // Create the KeyAccessServer object + KeyAccessServer kas1 = KeyAccessServer.newBuilder() + .setPublicKey(PublicKey.newBuilder() + .setCached(KasPublicKeySet.newBuilder() + .addAllKeys(kasPublicKeys) + .build())) + .setUri("https://example.com/kas") + .build(); + + // Add the KeyAccessServer to a list + List kases = List.of(kas1); + + // Call the method under test + Autoconfigure.storeKeysToCache(kases, keyCache); + + // Verify that the key was stored in the cache + Config.KASInfo storedKASInfo = keyCache.get("https://example.com/kas", "ec:secp256r1"); + assertNotNull(storedKASInfo); + assertEquals("https://example.com/kas", storedKASInfo.URL); + assertEquals("test-kid", storedKASInfo.KID); + assertEquals("ec:secp256r1", storedKASInfo.Algorithm); + assertEquals("public-key-pem", storedKASInfo.PublicKey); + + Config.KASInfo storedKASInfo2 = keyCache.get("https://example.com/kas", "rsa:2048"); + assertNotNull(storedKASInfo2); + assertEquals("https://example.com/kas", storedKASInfo2.URL); + assertEquals("test-kid-2", storedKASInfo2.KID); + assertEquals("rsa:2048", storedKASInfo2.Algorithm); + assertEquals("public-key-pem-2", storedKASInfo2.PublicKey); + } + + GetAttributeValuesByFqnsResponse getResponseWithGrants(GetAttributeValuesByFqnsRequest req, + List grants) { + GetAttributeValuesByFqnsResponse.Builder builder = GetAttributeValuesByFqnsResponse.newBuilder(); + + for (String v : req.getFqnsList()) { + AttributeValueFQN vfqn; + try { + vfqn = new AttributeValueFQN(v); + } catch (Exception e) { + return null; // Or throw the exception as needed + } + + Value val = Value.newBuilder(mockValueFor(vfqn)).addAllGrants(grants).build(); + + builder.putFqnAttributeValues(v, GetAttributeValuesByFqnsResponse.AttributeAndValue.newBuilder() + .setAttribute(val.getAttribute()) + .setValue(val) + .build()); + } + + return builder.build(); + } + + @Test + void testKeyCacheFromGrants() throws InterruptedException, ExecutionException { + // Create the KasPublicKey object + KasPublicKey kasPublicKey1 = KasPublicKey.newBuilder() + .setAlg(KasPublicKeyAlgEnum.KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP256R1) + .setKid("test-kid") + .setPem("public-key-pem") + .build(); + KasPublicKey kasPublicKey2 = KasPublicKey.newBuilder() + .setAlg(KasPublicKeyAlgEnum.KAS_PUBLIC_KEY_ALG_ENUM_RSA_2048) + .setKid("test-kid-2") + .setPem("public-key-pem-2") + .build(); + + // Add the KasPublicKey to a list + List kasPublicKeys = new ArrayList<>(); + kasPublicKeys.add(kasPublicKey1); + kasPublicKeys.add(kasPublicKey2); + + // Create the KeyAccessServer object + KeyAccessServer kas1 = KeyAccessServer.newBuilder() + .setPublicKey(PublicKey.newBuilder() + .setCached(KasPublicKeySet.newBuilder() + .addAllKeys(kasPublicKeys) + .build())) + .setUri("https://example.com/kas") + .build(); + +// AttributesServiceGrpc.AttributesServiceFutureStub attributeGrpcStub = mock( +// AttributesServiceGrpc.AttributesServiceFutureStub.class); +// lenient().when(attributeGrpcStub.getAttributeValuesByFqns(any(GetAttributeValuesByFqnsRequest.class))) +// .thenAnswer( +// invocation -> { +// GetAttributeValuesByFqnsResponse resp = getResponseWithGrants( +// (GetAttributeValuesByFqnsRequest) invocation.getArguments()[0], List.of(kas1)); +// SettableFuture future = SettableFuture.create(); +// future.set(resp); // Set the request as the future's result +// return future; +// }); +// AttributesServiceClient attributeGrpcStub = mock(AttributesServiceClient.class); +// lenient().when(attributeGrpcStub.getAttributeValuesByFqns(any(), any())) +// .thenAnswer( +// invocation -> { +// GetAttributeValuesByFqnsResponse resp = getResponseWithGrants( +// (GetAttributeValuesByFqnsRequest) invocation.getArguments()[0], List.of(kas1)); +// SettableFuture future = SettableFuture.create(); +// future.set(resp); // Set the request as the future's result +// return future; +// }); + // KASKeyCache keyCache = new KASKeyCache(); // -// // Create the KasPublicKey object -// KasPublicKey kasPublicKey1 = KasPublicKey.newBuilder() -// .setAlg(KasPublicKeyAlgEnum.KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP256R1) -// .setKid("test-kid") -// .setPem("public-key-pem") -// .build(); -// KasPublicKey kasPublicKey2 = KasPublicKey.newBuilder() -// .setAlg(KasPublicKeyAlgEnum.KAS_PUBLIC_KEY_ALG_ENUM_RSA_2048) -// .setKid("test-kid-2") -// .setPem("public-key-pem-2") -// .build(); -// -// // Add the KasPublicKey to a list -// List kasPublicKeys = new ArrayList<>(); -// kasPublicKeys.add(kasPublicKey1); -// kasPublicKeys.add(kasPublicKey2); -// -// // Create the KeyAccessServer object -// KeyAccessServer kas1 = KeyAccessServer.newBuilder() -// .setPublicKey(PublicKey.newBuilder() -// .setCached(KasPublicKeySet.newBuilder() -// .addAllKeys(kasPublicKeys) -// .build())) -// .setUri("https://example.com/kas") -// .build(); -// -// // Add the KeyAccessServer to a list -// List kases = List.of(kas1); -// -// // Call the method under test -// Autoconfigure.storeKeysToCache(kases, keyCache); +// Granter reasoner = Autoconfigure.newGranterFromService(attributeGrpcStub, keyCache, +// List.of(clsS, rel2gbr, rel2usa, n2kHCS, n2kSI).toArray(new AttributeValueFQN[0])); +// assertThat(reasoner).isNotNull(); // // // Verify that the key was stored in the cache // Config.KASInfo storedKASInfo = keyCache.get("https://example.com/kas", "ec:secp256r1"); @@ -844,102 +958,7 @@ // assertEquals("test-kid-2", storedKASInfo2.KID); // assertEquals("rsa:2048", storedKASInfo2.Algorithm); // assertEquals("public-key-pem-2", storedKASInfo2.PublicKey); -// } -// -// GetAttributeValuesByFqnsResponse getResponseWithGrants(GetAttributeValuesByFqnsRequest req, -// List grants) { -// GetAttributeValuesByFqnsResponse.Builder builder = GetAttributeValuesByFqnsResponse.newBuilder(); -// -// for (String v : req.getFqnsList()) { -// AttributeValueFQN vfqn; -// try { -// vfqn = new AttributeValueFQN(v); -// } catch (Exception e) { -// return null; // Or throw the exception as needed -// } -// -// Value val = Value.newBuilder(mockValueFor(vfqn)).addAllGrants(grants).build(); -// -// builder.putFqnAttributeValues(v, GetAttributeValuesByFqnsResponse.AttributeAndValue.newBuilder() -// .setAttribute(val.getAttribute()) -// .setValue(val) -// .build()); -// } -// -// return builder.build(); -// } -// -// @Test -// void testKeyCacheFromGrants() throws InterruptedException, ExecutionException { -// // Create the KasPublicKey object -// KasPublicKey kasPublicKey1 = KasPublicKey.newBuilder() -// .setAlg(KasPublicKeyAlgEnum.KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP256R1) -// .setKid("test-kid") -// .setPem("public-key-pem") -// .build(); -// KasPublicKey kasPublicKey2 = KasPublicKey.newBuilder() -// .setAlg(KasPublicKeyAlgEnum.KAS_PUBLIC_KEY_ALG_ENUM_RSA_2048) -// .setKid("test-kid-2") -// .setPem("public-key-pem-2") -// .build(); -// -// // Add the KasPublicKey to a list -// List kasPublicKeys = new ArrayList<>(); -// kasPublicKeys.add(kasPublicKey1); -// kasPublicKeys.add(kasPublicKey2); -// -// // Create the KeyAccessServer object -// KeyAccessServer kas1 = KeyAccessServer.newBuilder() -// .setPublicKey(PublicKey.newBuilder() -// .setCached(KasPublicKeySet.newBuilder() -// .addAllKeys(kasPublicKeys) -// .build())) -// .setUri("https://example.com/kas") -// .build(); -// -//// AttributesServiceGrpc.AttributesServiceFutureStub attributeGrpcStub = mock( -//// AttributesServiceGrpc.AttributesServiceFutureStub.class); -//// lenient().when(attributeGrpcStub.getAttributeValuesByFqns(any(GetAttributeValuesByFqnsRequest.class))) -//// .thenAnswer( -//// invocation -> { -//// GetAttributeValuesByFqnsResponse resp = getResponseWithGrants( -//// (GetAttributeValuesByFqnsRequest) invocation.getArguments()[0], List.of(kas1)); -//// SettableFuture future = SettableFuture.create(); -//// future.set(resp); // Set the request as the future's result -//// return future; -//// }); -//// AttributesServiceClient attributeGrpcStub = mock(AttributesServiceClient.class); -//// lenient().when(attributeGrpcStub.getAttributeValuesByFqns(any(), any())) -//// .thenAnswer( -//// invocation -> { -//// GetAttributeValuesByFqnsResponse resp = getResponseWithGrants( -//// (GetAttributeValuesByFqnsRequest) invocation.getArguments()[0], List.of(kas1)); -//// SettableFuture future = SettableFuture.create(); -//// future.set(resp); // Set the request as the future's result -//// return future; -//// }); -// -//// KASKeyCache keyCache = new KASKeyCache(); -//// -//// Granter reasoner = Autoconfigure.newGranterFromService(attributeGrpcStub, keyCache, -//// List.of(clsS, rel2gbr, rel2usa, n2kHCS, n2kSI).toArray(new AttributeValueFQN[0])); -//// assertThat(reasoner).isNotNull(); -//// -//// // Verify that the key was stored in the cache -//// Config.KASInfo storedKASInfo = keyCache.get("https://example.com/kas", "ec:secp256r1"); -//// assertNotNull(storedKASInfo); -//// assertEquals("https://example.com/kas", storedKASInfo.URL); -//// assertEquals("test-kid", storedKASInfo.KID); -//// assertEquals("ec:secp256r1", storedKASInfo.Algorithm); -//// assertEquals("public-key-pem", storedKASInfo.PublicKey); -//// -//// Config.KASInfo storedKASInfo2 = keyCache.get("https://example.com/kas", "rsa:2048"); -//// assertNotNull(storedKASInfo2); -//// assertEquals("https://example.com/kas", storedKASInfo2.URL); -//// assertEquals("test-kid-2", storedKASInfo2.KID); -//// assertEquals("rsa:2048", storedKASInfo2.Algorithm); -//// assertEquals("public-key-pem-2", storedKASInfo2.PublicKey); -//// -// } // -//} + } + +} diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/SDKBuilderTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/SDKBuilderTest.java index 55aef88d..fd192680 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/SDKBuilderTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/SDKBuilderTest.java @@ -1,5 +1,6 @@ package io.opentdf.platform.sdk; +import com.connectrpc.ResponseMessageKt; import com.google.protobuf.Struct; import com.google.protobuf.Value; import io.grpc.Metadata; @@ -42,6 +43,7 @@ import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.Base64; +import java.util.Collections; import java.util.concurrent.atomic.AtomicReference; import static org.assertj.core.api.Assertions.assertThat; @@ -271,7 +273,7 @@ public ServerCall.Listener interceptCall(ServerCall Date: Mon, 21 Apr 2025 09:36:09 -0400 Subject: [PATCH 08/52] move file --- .../kotlin/io/opentdf/platform/{ => sdk}/AuthInterceptor.kt | 0 .../test/java/io/opentdf/platform/sdk/AutoconfigureTest.java | 4 ---- 2 files changed, 4 deletions(-) rename sdk/src/main/kotlin/io/opentdf/platform/{ => sdk}/AuthInterceptor.kt (100%) diff --git a/sdk/src/main/kotlin/io/opentdf/platform/AuthInterceptor.kt b/sdk/src/main/kotlin/io/opentdf/platform/sdk/AuthInterceptor.kt similarity index 100% rename from sdk/src/main/kotlin/io/opentdf/platform/AuthInterceptor.kt rename to sdk/src/main/kotlin/io/opentdf/platform/sdk/AuthInterceptor.kt diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/AutoconfigureTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/AutoconfigureTest.java index 79a62d1e..ac7923a2 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/AutoconfigureTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/AutoconfigureTest.java @@ -6,7 +6,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -17,7 +16,6 @@ import io.opentdf.platform.policy.Attribute; import io.opentdf.platform.policy.Value; import io.opentdf.platform.policy.attributes.AttributesServiceClient; -import io.opentdf.platform.policy.attributes.AttributesServiceGrpc; import io.opentdf.platform.policy.attributes.GetAttributeValuesByFqnsRequest; import io.opentdf.platform.policy.attributes.GetAttributeValuesByFqnsResponse; import io.opentdf.platform.sdk.Autoconfigure.AttributeValueFQN; @@ -37,8 +35,6 @@ import org.junit.jupiter.api.Test; import org.mockito.Mockito; -import com.google.common.util.concurrent.SettableFuture; - import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; From 65ac48d7d231411b59b99b601303209924178c8f Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Mon, 21 Apr 2025 11:05:12 -0400 Subject: [PATCH 09/52] address normalization --- .../io/opentdf/platform/sdk/KASClient.java | 73 +++++++++++-------- .../io/opentdf/platform/sdk/SDKBuilder.java | 10 ++- .../opentdf/platform/sdk/KASClientTest.java | 64 +++++++++++----- 3 files changed, 97 insertions(+), 50 deletions(-) diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java b/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java index 0d6e4e78..b734be47 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java @@ -22,10 +22,13 @@ import io.opentdf.platform.sdk.TDF.KasBadRequestException; import kotlin.collections.MapsKt; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; + import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; import java.net.URL; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.time.Duration; import java.time.Instant; import java.util.Date; @@ -44,6 +47,7 @@ public class KASClient implements SDK.KAS { private final Function channelFactory; + private final boolean usePlaintext; private final RSASSASigner signer; private AsymDecryption decryptor; private String clientPublicKey; @@ -52,12 +56,13 @@ public class KASClient implements SDK.KAS { /*** * A client that communicates with KAS * - * @param channelFactory A function that produces channels that can be used to + * @param clientFactory A function that produces channels that can be used to * communicate * @param dpopKey */ - public KASClient(Function channelFactory, RSAKey dpopKey) { - this.channelFactory = channelFactory; + public KASClient(Function clientFactory, RSAKey dpopKey, boolean usePlaintext) { + this.channelFactory = clientFactory; + this.usePlaintext = usePlaintext; try { this.signer = new RSASSASigner(dpopKey); } catch (JOSEException e) { @@ -68,7 +73,7 @@ public KASClient(Function channelFactory, RSAKey dp @Override public KASInfo getECPublicKey(Config.KASInfo kasInfo, NanoTDFType.ECCurve curve) { - var req = PublicKeyRequest.newBuilder().setAlgorithm(String.format("ec:%s", curve.toString())).build(); + var req = PublicKeyRequest.newBuilder().setAlgorithm(format("ec:%s", curve.toString())).build(); var r = getStub(kasInfo.URL).publicKeyBlocking(req, MapsKt.mapOf()).execute(); PublicKeyResponse res; try { @@ -117,31 +122,35 @@ public KASKeyCache getKeyCache() { } private String normalizeAddress(String urlString) { - return urlString; -// URL url; -// try { -// url = new URL(urlString); -// } catch (MalformedURLException e) { -// // if there is no protocol then they either gave us -// // a correct address or one we don't know how to fix -// return urlString; -// } -// -// // otherwise we take the specified port or default -// // based on whether the URL uses a scheme that -// // implies TLS -// int port; -// if (url.getPort() == -1) { -// if ("http".equals(url.getProtocol())) { -// port = 80; -// } else { -// port = 443; -// } -// } else { -// port = url.getPort(); -// } -// -// return format("%s:%d", url.getHost(), port); + URL url; + try { + url = new URL(urlString); + } catch (MalformedURLException e) { + url = tryParseHostAndPort(urlString); + } + final int port = url.getPort() == -1 ? ("http".equals(url.getProtocol()) ? 80 : 443 ) : url.getPort(); + final String protocol = usePlaintext && "http".equals(url.getProtocol()) ? "http" : "https"; + + try { + return new URL(protocol, url.getHost(), port, "").toString(); + } catch (MalformedURLException e) { + throw new SDKException("error creating KAS address", e); + } + } + + private URL tryParseHostAndPort(String urlString) { + URI uri; + try { + uri = new URI(null, urlString, null, null, null).parseServerAuthority(); + } catch (URISyntaxException e) { + throw new SDKException("error trying to parse host and port", e); + } + + try { + return new URL(uri.getPort() == 80 ? "http" : "https", uri.getHost(), uri.getPort(), ""); + } catch (MalformedURLException e) { + throw new SDKException("error trying to create URL from host and port", e); + } } @Override @@ -257,7 +266,7 @@ public byte[] unwrapNanoTDF(NanoTDFType.ECCurve curve, String header, String kas keyAccess.protocol = "kas"; NanoTDFRewrapRequestBody body = new NanoTDFRewrapRequestBody(); - body.algorithm = String.format("ec:%s", curve.toString()); + body.algorithm = format("ec:%s", curve.toString()); body.clientPublicKey = keyPair.publicKeyInPEMFormat(); body.keyAccess = keyAccess; diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java b/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java index 6771198c..ecb312ef 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java @@ -143,6 +143,14 @@ public SDKBuilder clientSecret(String clientID, String clientSecret) { return this; } + /** + * If set to `true` `http` connections to platform services are allowed. In particular, + * use this option to unwrap TDFs using KASs that are not using TLS. Also, if KASs use + * : addresses then this option must be set in order for the SDK to + * call the KAS without TLS. + * @param usePlainText + * @return + */ public SDKBuilder useInsecurePlaintextConnection(Boolean usePlainText) { this.usePlainText = usePlainText; return this; @@ -264,7 +272,7 @@ private KASClient getKASClient(RSAKey dpopKey, Interceptor interceptor) { return new AccessServiceClient(as); }; - return new KASClient(clientFactory, dpopKey); + return new KASClient(clientFactory, dpopKey, false); } public SDK build() { diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/KASClientTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/KASClientTest.java index 0e245517..13d08dd1 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/KASClientTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/KASClientTest.java @@ -24,8 +24,6 @@ import io.opentdf.platform.kas.RewrapResponse; import okhttp3.OkHttpClient; import okhttp3.Protocol; -import org.junit.Ignore; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import java.io.IOException; @@ -74,7 +72,7 @@ public void publicKey(PublicKeyRequest request, StreamObserver respons rewrapServer = startServer(accessService); byte[] plaintextKey; byte[] rewrapResponse; - try (var kas = new KASClient(aclientFactory, dpopKey)) { + try (var kas = new KASClient(aclientFactory, dpopKey, false)) { Manifest.KeyAccess keyAccess = new Manifest.KeyAccess(); keyAccess.url = "http://localhost:" + rewrapServer.getPort(); @@ -188,28 +186,60 @@ public void rewrap(RewrapRequest request, StreamObserver respons } } - @Test @Disabled("not quite sure what are address normalization requirements are now") - public void testAddressNormalization() { + @Test + public void testAddressNormalizationWithHTTPSClient() { var lastAddress = new AtomicReference(); var dpopKeypair = CryptoUtils.generateRSAKeypair(); var dpopKey = new RSAKey.Builder((RSAPublicKey) dpopKeypair.getPublic()).privateKey(dpopKeypair.getPrivate()) .build(); - var kasClient = new KASClient(addr -> { + var httpsKASClient = new KASClient(addr -> { lastAddress.set(addr); return aclientFactory.apply(addr); - }, dpopKey); + }, dpopKey, false); - var stub = kasClient.getStub("http://localhost:8080"); - assertThat(lastAddress.get()).isEqualTo("localhost:8080"); - var otherStub = kasClient.getStub("https://localhost:8080"); - assertThat(lastAddress.get()).isEqualTo("localhost:8080"); + var stub = httpsKASClient.getStub("http://localhost:8080"); + assertThat(lastAddress.get()).isEqualTo("https://localhost:8080"); + var otherStub = httpsKASClient.getStub("https://localhost:8080"); + assertThat(lastAddress.get()).isEqualTo("https://localhost:8080"); assertThat(stub).isSameAs(otherStub); - kasClient.getStub("https://example.org"); - assertThat(lastAddress.get()).isEqualTo("example.org:443"); + httpsKASClient.getStub("https://example.org"); + assertThat(lastAddress.get()).isEqualTo("https://example.org:443"); + + httpsKASClient.getStub("http://example.org"); + assertThat(lastAddress.get()).isEqualTo("https://example.org:80"); + + httpsKASClient.getStub("example.org:1234"); + assertThat(lastAddress.get()).isEqualTo("https://example.org:1234"); + } + + @Test + public void testAddressNormalizationWithInsecureHTTPClient() { + var lastAddress = new AtomicReference(); + var dpopKeypair = CryptoUtils.generateRSAKeypair(); + var dpopKey = new RSAKey.Builder((RSAPublicKey) dpopKeypair.getPublic()).privateKey(dpopKeypair.getPrivate()) + .build(); + var httpsKASClient = new KASClient(addr -> { + lastAddress.set(addr); + return aclientFactory.apply(addr); + }, dpopKey, true); + + httpsKASClient.getStub("http://localhost:8080"); + assertThat(lastAddress.get()).isEqualTo("http://localhost:8080"); + assertThat(lastAddress.get()).isEqualTo("http://localhost:8080"); + + httpsKASClient.getStub("https://example.org"); + assertThat(lastAddress.get()).isEqualTo("https://example.org:443"); + + var c1 = httpsKASClient.getStub("http://example.org"); + assertThat(lastAddress.get()).isEqualTo("http://example.org:80"); + var c2 = httpsKASClient.getStub("example.org:80"); + assertThat(lastAddress.get()).isEqualTo("http://example.org:80"); + assertThat(c1).isSameAs(c2); + + httpsKASClient.getStub("example.org:1234"); + assertThat(lastAddress.get()).isEqualTo("https://example.org:1234"); - kasClient.getStub("http://example.org"); - assertThat(lastAddress.get()).isEqualTo("example.org:80"); } private static Server startServer(AccessServiceGrpc.AccessServiceImplBase accessService) throws IOException { From 437ea209bb96dff3037da7ba7e6f09b7f4b63940 Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Mon, 21 Apr 2025 11:11:03 -0400 Subject: [PATCH 10/52] fix test defaults --- .../test/java/io/opentdf/platform/sdk/KASClientTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/KASClientTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/KASClientTest.java index 13d08dd1..9fe85bdd 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/KASClientTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/KASClientTest.java @@ -72,7 +72,7 @@ public void publicKey(PublicKeyRequest request, StreamObserver respons rewrapServer = startServer(accessService); byte[] plaintextKey; byte[] rewrapResponse; - try (var kas = new KASClient(aclientFactory, dpopKey, false)) { + try (var kas = new KASClient(aclientFactory, dpopKey, true)) { Manifest.KeyAccess keyAccess = new Manifest.KeyAccess(); keyAccess.url = "http://localhost:" + rewrapServer.getPort(); From b1e3b281ee30eaf040ade49b46eadc24cd0c3268 Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Mon, 21 Apr 2025 12:04:57 -0400 Subject: [PATCH 11/52] still need that --- sdk/pom.xml | 66 ++++++++++++++++++++++++++--------------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/sdk/pom.xml b/sdk/pom.xml index 6a46ae79..6f334757 100644 --- a/sdk/pom.xml +++ b/sdk/pom.xml @@ -377,39 +377,39 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + org.apache.maven.plugins + maven-antrun-plugin + 3.1.0 + + + + generateSources + generate-sources + + + + + + + + + + + + + + + + + + + + run + + + + org.codehaus.mojo From 4a09aebe2273415eea771ac6f6754eb48380707e Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Mon, 21 Apr 2025 12:11:37 -0400 Subject: [PATCH 12/52] propogate plaintext setting --- sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java b/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java index ecb312ef..a35e4ab1 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java @@ -272,7 +272,7 @@ private KASClient getKASClient(RSAKey dpopKey, Interceptor interceptor) { return new AccessServiceClient(as); }; - return new KASClient(clientFactory, dpopKey, false); + return new KASClient(clientFactory, dpopKey, usePlainText); } public SDK build() { From fc81bf1aba1febbae86c2ac0aab7f8dfc15503a1 Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Mon, 21 Apr 2025 12:21:58 -0400 Subject: [PATCH 13/52] disable to see if tests work --- sdk/pom.xml | 38 +++++++++---------- .../io/opentdf/platform/sdk/SDKBuilder.java | 4 -- 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/sdk/pom.xml b/sdk/pom.xml index 963792a9..2154cbb1 100644 --- a/sdk/pom.xml +++ b/sdk/pom.xml @@ -412,25 +412,25 @@ - - org.codehaus.mojo - build-helper-maven-plugin - 3.5.0 - - - add-source - generate-sources - - add-source - - - - target/generated-sources - - - - - + + + + + + + + + + + + + + + + + + + org.jacoco jacoco-maven-plugin diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java b/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java index a35e4ab1..8fd12002 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java @@ -257,7 +257,6 @@ private KASClient getKASClient(RSAKey dpopKey, Interceptor interceptor) { if (sslFactory != null) { c.sslSocketFactory(sslFactory.getSslSocketFactory(), sslFactory.getTrustManager().get()); } - var as = new ProtocolClient( new ConnectOkHttpClient(c.build()), new ProtocolClientConfig(endpoint, @@ -268,10 +267,8 @@ private KASClient getKASClient(RSAKey dpopKey, Interceptor interceptor) { List.of(_ignored -> interceptor) ) ); - return new AccessServiceClient(as); }; - return new KASClient(clientFactory, dpopKey, usePlainText); } @@ -305,7 +302,6 @@ private ProtocolClient getProtocolClient(String endpoint, Interceptor intercepto return new ProtocolClient(new ConnectOkHttpClient(httpClient.build()), protocolClientConfig); } - SSLFactory getSslFactory() { return this.sslFactory; } From 20a10b3f342f4003f1b9b40a9876d3473487ddac Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Mon, 21 Apr 2025 13:54:33 -0400 Subject: [PATCH 14/52] do not document generated code --- sdk/buf.gen.yaml | 4 +- sdk/pom.xml | 41 ++++++++++--------- .../opentdf/platform/sdk/Autoconfigure.java | 28 ++++++------- .../java/io/opentdf/platform/sdk/Config.java | 3 +- .../io/opentdf/platform/sdk/KASClient.java | 21 ++++++---- .../io/opentdf/platform/sdk/SDKBuilder.java | 8 ++-- .../java/io/opentdf/platform/sdk/TDF.java | 4 +- 7 files changed, 57 insertions(+), 52 deletions(-) diff --git a/sdk/buf.gen.yaml b/sdk/buf.gen.yaml index 31b61143..9fffa792 100644 --- a/sdk/buf.gen.yaml +++ b/sdk/buf.gen.yaml @@ -10,7 +10,7 @@ managed: module: buf.build/grpc-ecosystem/grpc-gateway override: - file_option: java_package_prefix - value: io.opentdf.platform + value: io.opentdf.platform.generated plugins: - remote: buf.build/protocolbuffers/java:v25.3 out: ./ @@ -19,6 +19,4 @@ plugins: - remote: buf.build/connectrpc/kotlin out: ./ opt: - - generateCallbackMethods=true - - generateCoroutineMethods=true - generateBlockingUnaryMethods=true diff --git a/sdk/pom.xml b/sdk/pom.xml index 2154cbb1..cfb8e0d0 100644 --- a/sdk/pom.xml +++ b/sdk/pom.xml @@ -337,6 +337,9 @@ org.apache.maven.plugins maven-javadoc-plugin 3.8.0 + + ${project.basedir}/src/main/**/*.java + attach-javadocs @@ -412,25 +415,25 @@ - - - - - - - - - - - - - - - - - - - + + org.codehaus.mojo + build-helper-maven-plugin + 3.5.0 + + + add-source + generate-sources + + add-source + + + + target/generated-sources + + + + + org.jacoco jacoco-maven-plugin diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/Autoconfigure.java b/sdk/src/main/java/io/opentdf/platform/sdk/Autoconfigure.java index 08c98f91..74d910eb 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/Autoconfigure.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/Autoconfigure.java @@ -1,18 +1,16 @@ package io.opentdf.platform.sdk; -import com.connectrpc.ResponseMessage; import com.connectrpc.ResponseMessageKt; -import io.opentdf.platform.policy.Attribute; -import io.opentdf.platform.policy.AttributeRuleTypeEnum; -import io.opentdf.platform.policy.AttributeValueSelector; -import io.opentdf.platform.policy.KasPublicKey; -import io.opentdf.platform.policy.KasPublicKeyAlgEnum; -import io.opentdf.platform.policy.KeyAccessServer; -import io.opentdf.platform.policy.Value; -import io.opentdf.platform.policy.attributes.AttributesServiceClient; -import io.opentdf.platform.policy.attributes.GetAttributeValuesByFqnsRequest; -import io.opentdf.platform.policy.attributes.GetAttributeValuesByFqnsResponse; -import io.opentdf.platform.policy.attributes.GetAttributeValuesByFqnsResponse.AttributeAndValue; +import io.opentdf.platform.generated.policy.Attribute; +import io.opentdf.platform.generated.policy.AttributeRuleTypeEnum; +import io.opentdf.platform.generated.policy.AttributeValueSelector; +import io.opentdf.platform.generated.policy.KasPublicKey; +import io.opentdf.platform.generated.policy.KasPublicKeyAlgEnum; +import io.opentdf.platform.generated.policy.KeyAccessServer; +import io.opentdf.platform.generated.policy.Value; +import io.opentdf.platform.generated.policy.attributes.AttributesServiceClient; +import io.opentdf.platform.generated.policy.attributes.GetAttributeValuesByFqnsRequest; +import io.opentdf.platform.generated.policy.attributes.GetAttributeValuesByFqnsResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -690,7 +688,7 @@ public static Granter newGranterFromAttributes(Value... attrValues) throws AutoC if (!v.hasAttribute()) { throw new AutoConfigureException("tried to use an attribute that is not initialized"); } - return AttributeAndValue.newBuilder() + return GetAttributeValuesByFqnsResponse.AttributeAndValue.newBuilder() .setValue(v) .setAttribute(v.getAttribute()) .build(); @@ -713,8 +711,8 @@ public static Granter newGranterFromService(AttributesServiceClient as, KASKeyCa return getGranter(keyCache, new ArrayList<>(av.getFqnAttributeValuesMap().values())); } - private static Granter getGranter(@Nullable KASKeyCache keyCache, List values) { - Granter grants = new Granter(values.stream().map(AttributeAndValue::getValue).map(Value::getFqn).map(AttributeValueFQN::new).collect(Collectors.toList())); + private static Granter getGranter(@Nullable KASKeyCache keyCache, List values) { + Granter grants = new Granter(values.stream().map(GetAttributeValuesByFqnsResponse.AttributeAndValue::getValue).map(Value::getFqn).map(AttributeValueFQN::new).collect(Collectors.toList())); for (var attributeAndValue: values) { var val = attributeAndValue.getValue(); diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/Config.java b/sdk/src/main/java/io/opentdf/platform/sdk/Config.java index 3e46649d..55d8130d 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/Config.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/Config.java @@ -1,13 +1,12 @@ package io.opentdf.platform.sdk; +import io.opentdf.platform.generated.policy.Value; import io.opentdf.platform.sdk.Autoconfigure.AttributeValueFQN; import io.opentdf.platform.sdk.nanotdf.ECCMode; import io.opentdf.platform.sdk.nanotdf.Header; import io.opentdf.platform.sdk.nanotdf.NanoTDFType; import io.opentdf.platform.sdk.nanotdf.SymmetricAndPayloadConfig; -import io.opentdf.platform.policy.Value; - import java.util.*; import java.util.function.Consumer; diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java b/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java index b734be47..6e3b72ca 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java @@ -11,11 +11,11 @@ import com.nimbusds.jwt.SignedJWT; import io.grpc.StatusRuntimeException; import io.grpc.Status; -import io.opentdf.platform.kas.AccessServiceClient; -import io.opentdf.platform.kas.PublicKeyRequest; -import io.opentdf.platform.kas.PublicKeyResponse; -import io.opentdf.platform.kas.RewrapRequest; -import io.opentdf.platform.kas.RewrapResponse; +import io.opentdf.platform.generated.kas.AccessServiceClient; +import io.opentdf.platform.generated.kas.PublicKeyRequest; +import io.opentdf.platform.generated.kas.PublicKeyResponse; +import io.opentdf.platform.generated.kas.RewrapRequest; +import io.opentdf.platform.generated.kas.RewrapResponse; import io.opentdf.platform.sdk.Config.KASInfo; import io.opentdf.platform.sdk.nanotdf.ECKeyPair; import io.opentdf.platform.sdk.nanotdf.NanoTDFType; @@ -31,6 +31,7 @@ import java.security.NoSuchAlgorithmException; import java.time.Duration; import java.time.Instant; +import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.function.Function; @@ -71,6 +72,12 @@ public KASClient(Function clientFactory, RSAKey dpo this.kasKeyCache = new KASKeyCache(); } + public KASClient(Function channelFactory, boolean usePlaintext, RSASSASigner signer) { + this.channelFactory = channelFactory; + this.usePlaintext = usePlaintext; + this.signer = signer; + } + @Override public KASInfo getECPublicKey(Config.KASInfo kasInfo, NanoTDFType.ECCurve curve) { var req = PublicKeyRequest.newBuilder().setAlgorithm(format("ec:%s", curve.toString())).build(); @@ -221,7 +228,7 @@ public byte[] unwrap(Manifest.KeyAccess keyAccess, String policy, KeyType sessi .build(); RewrapResponse response; try { - var req = getStub(keyAccess.url).rewrapBlocking(request, MapsKt.mapOf()).execute(); + var req = getStub(keyAccess.url).rewrapBlocking(request, Collections.emptyMap()).execute(); try { response = getOrThrow(req); } catch (Exception e) { @@ -290,7 +297,7 @@ public byte[] unwrapNanoTDF(NanoTDFType.ECCurve curve, String header, String kas .setSignedRequestToken(jwt.serialize()) .build(); - var request = getStub(keyAccess.url).rewrapBlocking(req, MapsKt.mapOf()).execute(); + var request = getStub(keyAccess.url).rewrapBlocking(req, Collections.emptyMap()).execute(); RewrapResponse response; try { response = getOrThrow(request); diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java b/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java index 8fd12002..e75184c3 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java @@ -26,10 +26,10 @@ import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata; import io.grpc.Status; import io.grpc.StatusRuntimeException; -import io.opentdf.platform.kas.AccessServiceClient; -import io.opentdf.platform.wellknownconfiguration.GetWellKnownConfigurationRequest; -import io.opentdf.platform.wellknownconfiguration.GetWellKnownConfigurationResponse; -import io.opentdf.platform.wellknownconfiguration.WellKnownServiceClient; +import io.opentdf.platform.generated.kas.AccessServiceClient; +import io.opentdf.platform.generated.wellknownconfiguration.GetWellKnownConfigurationRequest; +import io.opentdf.platform.generated.wellknownconfiguration.GetWellKnownConfigurationResponse; +import io.opentdf.platform.generated.wellknownconfiguration.WellKnownServiceClient; import nl.altindag.ssl.SSLFactory; import nl.altindag.ssl.pem.util.PemUtils; import okhttp3.OkHttpClient; diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java b/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java index 407c5e55..fe92476c 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java @@ -4,8 +4,8 @@ import com.google.gson.GsonBuilder; import com.nimbusds.jose.*; -import io.opentdf.platform.policy.Value; -import io.opentdf.platform.policy.attributes.AttributesServiceClient; +import io.opentdf.platform.generated.policy.Value; +import io.opentdf.platform.generated.policy.attributes.AttributesServiceClient; import io.opentdf.platform.sdk.Config.TDFConfig; import io.opentdf.platform.sdk.Autoconfigure.AttributeValueFQN; import io.opentdf.platform.sdk.Config.KASInfo; From 531a01986e30297ec56a78da7d6b01a43d063c4f Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Mon, 21 Apr 2025 14:38:32 -0400 Subject: [PATCH 15/52] update examples --- examples/buf.gen.yaml | 14 ----- examples/buf.lock | 18 ------- examples/buf.yaml | 22 -------- examples/pom.xml | 53 ------------------- .../io/opentdf/platform/CreateAttribute.java | 13 ++--- .../io/opentdf/platform/CreateNamespace.java | 8 +-- .../platform/CreateSubjectConditionSet.java | 22 ++++---- .../platform/CreateSubjectMapping.java | 13 +++-- .../io/opentdf/platform/GetDecisions.java | 15 ++++-- .../io/opentdf/platform/GetEntitlements.java | 10 ++-- .../io/opentdf/platform/ListAttributes.java | 12 ++--- .../io/opentdf/platform/ListNamespaces.java | 13 +++-- .../opentdf/platform/ListSubjectMappings.java | 10 ++-- .../java/io/opentdf/platform/sdk/SDK.java | 12 ++--- .../platform/sdk/AutoconfigureTest.java | 24 ++++----- .../opentdf/platform/sdk/KASClientTest.java | 12 ++--- .../opentdf/platform/sdk/SDKBuilderTest.java | 22 ++++---- 17 files changed, 105 insertions(+), 188 deletions(-) delete mode 100644 examples/buf.gen.yaml delete mode 100644 examples/buf.lock delete mode 100644 examples/buf.yaml diff --git a/examples/buf.gen.yaml b/examples/buf.gen.yaml deleted file mode 100644 index 6bb9c4e3..00000000 --- a/examples/buf.gen.yaml +++ /dev/null @@ -1,14 +0,0 @@ -version: v1 -managed: - enabled: true - java_package_prefix: - default: io.opentdf.platform - except: - - buf.build/bufbuild/protovalidate - - buf.build/googleapis/googleapis - - buf.build/grpc-ecosystem/grpc-gateway -plugins: - - plugin: buf.build/protocolbuffers/java:v25.3 - out: ./ - - plugin: buf.build/grpc/java:v1.61.1 - out: ./ diff --git a/examples/buf.lock b/examples/buf.lock deleted file mode 100644 index deef61e8..00000000 --- a/examples/buf.lock +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by buf. DO NOT EDIT. -version: v1 -deps: - - remote: buf.build - owner: bufbuild - repository: protovalidate - commit: f05a6f4403ce4327bae4f50f281c3ed0 - digest: shake256:668a0661b8df44d41839194896329330965fc215f3d2f88057fd60eeb759c2daf6cc6edfdd13b2a653d49fe2896ebedcb1a33c4c5b2dd10919f03ffb7fc52ae6 - - remote: buf.build - owner: googleapis - repository: googleapis - commit: 7e6f6e774e29406da95bd61cdcdbc8bc - digest: shake256:fe43dd2265ea0c07d76bd925eeba612667cf4c948d2ce53d6e367e1b4b3cb5fa69a51e6acb1a6a50d32f894f054a35e6c0406f6808a483f2752e10c866ffbf73 - - remote: buf.build - owner: grpc-ecosystem - repository: grpc-gateway - commit: 3f42134f4c564983838425bc43c7a65f - digest: shake256:3d11d4c0fe5e05fda0131afefbce233940e27f0c31c5d4e385686aea58ccd30f72053f61af432fa83f1fc11cda57f5f18ca3da26a29064f73c5a0d076bba8d92 \ No newline at end of file diff --git a/examples/buf.yaml b/examples/buf.yaml deleted file mode 100644 index 2dc8eb0e..00000000 --- a/examples/buf.yaml +++ /dev/null @@ -1,22 +0,0 @@ -version: v1 -deps: - - buf.build/bufbuild/protovalidate - - buf.build/googleapis/googleapis - - buf.build/grpc-ecosystem/grpc-gateway -breaking: - use: - - FILE - - PACKAGE - - WIRE_JSON - - WIRE -lint: - allow_comment_ignores: true - use: - - DEFAULT - except: - - PACKAGE_VERSION_SUFFIX - ignore_only: - PACKAGE_VERSION_SUFFIX: - - google/api/annotations.proto - - google/api/http.proto - - google/protobuf/wrappers.proto diff --git a/examples/pom.xml b/examples/pom.xml index c7976094..d4d03e0c 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -125,59 +125,6 @@ io.opentdf.platform.App - - org.apache.maven.plugins - maven-antrun-plugin - 3.1.0 - - - - generateSources - generate-sources - - - - - - - - - - - - - - - run - - - - - - - org.codehaus.mojo - build-helper-maven-plugin - 3.5.0 - - - add-source - generate-sources - - add-source - - - - target/generated-sources - - - - - diff --git a/examples/src/main/java/io/opentdf/platform/CreateAttribute.java b/examples/src/main/java/io/opentdf/platform/CreateAttribute.java index ba72189e..b5fdba33 100644 --- a/examples/src/main/java/io/opentdf/platform/CreateAttribute.java +++ b/examples/src/main/java/io/opentdf/platform/CreateAttribute.java @@ -1,13 +1,14 @@ package io.opentdf.platform; +import com.connectrpc.ResponseMessageKt; +import io.opentdf.platform.generated.policy.Attribute; +import io.opentdf.platform.generated.policy.AttributeRuleTypeEnum; +import io.opentdf.platform.generated.policy.attributes.CreateAttributeRequest; +import io.opentdf.platform.generated.policy.attributes.CreateAttributeResponse; import io.opentdf.platform.sdk.*; +import java.util.Collections; import java.util.concurrent.ExecutionException; -import io.opentdf.platform.policy.AttributeRuleTypeEnum; - -import io.opentdf.platform.policy.attributes.*; -import io.opentdf.platform.policy.Attribute; - import java.util.Arrays; public class CreateAttribute { @@ -28,7 +29,7 @@ public static void main(String[] args) throws ExecutionException, InterruptedExc .setRule(AttributeRuleTypeEnum.forNumber(AttributeRuleTypeEnum.ATTRIBUTE_RULE_TYPE_ENUM_ALL_OF_VALUE)) .addAllValues(Arrays.asList("test1", "test2")).build(); - CreateAttributeResponse resp = sdk.getServices().attributes().createAttribute(request).get(); + CreateAttributeResponse resp = ResponseMessageKt.getOrThrow(sdk.getServices().attributes().createAttributeBlocking(request, Collections.emptyMap()).execute()); Attribute attribute = resp.getAttribute(); diff --git a/examples/src/main/java/io/opentdf/platform/CreateNamespace.java b/examples/src/main/java/io/opentdf/platform/CreateNamespace.java index 112cde4f..0a85a7f7 100644 --- a/examples/src/main/java/io/opentdf/platform/CreateNamespace.java +++ b/examples/src/main/java/io/opentdf/platform/CreateNamespace.java @@ -1,10 +1,12 @@ package io.opentdf.platform; +import com.connectrpc.ResponseMessageKt; +import io.opentdf.platform.generated.policy.namespaces.CreateNamespaceRequest; +import io.opentdf.platform.generated.policy.namespaces.CreateNamespaceResponse; import io.opentdf.platform.sdk.*; +import java.util.Collections; import java.util.concurrent.ExecutionException; -import io.opentdf.platform.policy.namespaces.*; - public class CreateNamespace { public static void main(String[] args) throws ExecutionException, InterruptedException{ @@ -19,7 +21,7 @@ public static void main(String[] args) throws ExecutionException, InterruptedExc CreateNamespaceRequest request = CreateNamespaceRequest.newBuilder().setName("mynamespace.com").build(); - CreateNamespaceResponse resp = sdk.getServices().namespaces().createNamespace(request).get(); + CreateNamespaceResponse resp = ResponseMessageKt.getOrThrow(sdk.getServices().namespaces().createNamespaceBlocking(request, Collections.emptyMap()).execute()); System.out.println(resp.getNamespace().getName()); diff --git a/examples/src/main/java/io/opentdf/platform/CreateSubjectConditionSet.java b/examples/src/main/java/io/opentdf/platform/CreateSubjectConditionSet.java index 0eb6c5a2..65eb18c0 100644 --- a/examples/src/main/java/io/opentdf/platform/CreateSubjectConditionSet.java +++ b/examples/src/main/java/io/opentdf/platform/CreateSubjectConditionSet.java @@ -1,17 +1,19 @@ package io.opentdf.platform; +import com.connectrpc.ResponseMessageKt; +import io.opentdf.platform.generated.policy.Condition; +import io.opentdf.platform.generated.policy.ConditionBooleanTypeEnum; +import io.opentdf.platform.generated.policy.ConditionGroup; +import io.opentdf.platform.generated.policy.SubjectConditionSet; +import io.opentdf.platform.generated.policy.SubjectMappingOperatorEnum; +import io.opentdf.platform.generated.policy.SubjectSet; +import io.opentdf.platform.generated.policy.subjectmapping.CreateSubjectConditionSetRequest; +import io.opentdf.platform.generated.policy.subjectmapping.CreateSubjectConditionSetResponse; +import io.opentdf.platform.generated.policy.subjectmapping.SubjectConditionSetCreate; import io.opentdf.platform.sdk.*; +import java.util.Collections; import java.util.concurrent.ExecutionException; -import io.opentdf.platform.policy.subjectmapping.*; -import io.opentdf.platform.policy.SubjectMapping; -import io.opentdf.platform.policy.SubjectConditionSet; -import io.opentdf.platform.policy.SubjectSet; -import io.opentdf.platform.policy.ConditionGroup; -import io.opentdf.platform.policy.Condition; -import io.opentdf.platform.policy.ConditionBooleanTypeEnum; -import io.opentdf.platform.policy.SubjectMappingOperatorEnum; - public class CreateSubjectConditionSet { public static void main(String[] args) throws ExecutionException, InterruptedException{ @@ -38,7 +40,7 @@ public static void main(String[] args) throws ExecutionException, InterruptedExc SubjectConditionSetCreate.newBuilder().addSubjectSets(subjectset)) .build(); - CreateSubjectConditionSetResponse resp = sdk.getServices().subjectMappings().createSubjectConditionSet(request).get(); + CreateSubjectConditionSetResponse resp = ResponseMessageKt.getOrThrow(sdk.getServices().subjectMappings().createSubjectConditionSetBlocking(request, Collections.emptyMap()).execute()); SubjectConditionSet scs = resp.getSubjectConditionSet(); diff --git a/examples/src/main/java/io/opentdf/platform/CreateSubjectMapping.java b/examples/src/main/java/io/opentdf/platform/CreateSubjectMapping.java index 41f07336..efe40537 100644 --- a/examples/src/main/java/io/opentdf/platform/CreateSubjectMapping.java +++ b/examples/src/main/java/io/opentdf/platform/CreateSubjectMapping.java @@ -1,12 +1,15 @@ package io.opentdf.platform; +import com.connectrpc.ResponseMessageKt; +import grpc.gateway.protoc_gen_openapiv2.options.Response; +import io.opentdf.platform.generated.policy.Action; +import io.opentdf.platform.generated.policy.SubjectMapping; +import io.opentdf.platform.generated.policy.subjectmapping.CreateSubjectMappingRequest; +import io.opentdf.platform.generated.policy.subjectmapping.CreateSubjectMappingResponse; import io.opentdf.platform.sdk.*; +import java.util.Collections; import java.util.concurrent.ExecutionException; -import io.opentdf.platform.policy.subjectmapping.*; -import io.opentdf.platform.policy.SubjectMapping; -import io.opentdf.platform.policy.Action; - public class CreateSubjectMapping { public static void main(String[] args) throws ExecutionException, InterruptedException{ @@ -25,7 +28,7 @@ public static void main(String[] args) throws ExecutionException, InterruptedExc .setExistingSubjectConditionSetId("9009fde8-d22b-4dfb-a456-f9ce6943244a") .build(); - CreateSubjectMappingResponse resp = sdk.getServices().subjectMappings().createSubjectMapping(request).get(); + CreateSubjectMappingResponse resp = ResponseMessageKt.getOrThrow(sdk.getServices().subjectMappings().createSubjectMappingBlocking(request, Collections.emptyMap()).execute()); SubjectMapping sm = resp.getSubjectMapping(); diff --git a/examples/src/main/java/io/opentdf/platform/GetDecisions.java b/examples/src/main/java/io/opentdf/platform/GetDecisions.java index 4bac7694..4a90e9f4 100644 --- a/examples/src/main/java/io/opentdf/platform/GetDecisions.java +++ b/examples/src/main/java/io/opentdf/platform/GetDecisions.java @@ -1,11 +1,18 @@ package io.opentdf.platform; +import com.connectrpc.ResponseMessageKt; +import io.opentdf.platform.generated.authorization.DecisionRequest; +import io.opentdf.platform.generated.authorization.DecisionResponse; +import io.opentdf.platform.generated.authorization.Entity; +import io.opentdf.platform.generated.authorization.EntityChain; +import io.opentdf.platform.generated.authorization.GetDecisionsRequest; +import io.opentdf.platform.generated.authorization.GetDecisionsResponse; +import io.opentdf.platform.generated.authorization.ResourceAttribute; +import io.opentdf.platform.generated.policy.Action; import io.opentdf.platform.sdk.*; +import java.util.Collections; import java.util.concurrent.ExecutionException; -import io.opentdf.platform.authorization.*; -import io.opentdf.platform.policy.Action; - import java.util.List; public class GetDecisions { @@ -28,7 +35,7 @@ public static void main(String[] args) throws ExecutionException, InterruptedExc .addAttributeValueFqns("https://mynamespace.com/attr/test/value/test1")) ).build(); - GetDecisionsResponse resp = sdk.getServices().authorization().getDecisions(request).get(); + GetDecisionsResponse resp = ResponseMessageKt.getOrThrow(sdk.getServices().authorization().getDecisionsBlocking(request, Collections.emptyMap()).execute()); List decisions = resp.getDecisionResponsesList(); diff --git a/examples/src/main/java/io/opentdf/platform/GetEntitlements.java b/examples/src/main/java/io/opentdf/platform/GetEntitlements.java index ff484b80..ef50fd49 100644 --- a/examples/src/main/java/io/opentdf/platform/GetEntitlements.java +++ b/examples/src/main/java/io/opentdf/platform/GetEntitlements.java @@ -1,10 +1,14 @@ package io.opentdf.platform; +import com.connectrpc.ResponseMessageKt; +import io.opentdf.platform.generated.authorization.Entity; +import io.opentdf.platform.generated.authorization.EntityEntitlements; +import io.opentdf.platform.generated.authorization.GetEntitlementsRequest; +import io.opentdf.platform.generated.authorization.GetEntitlementsResponse; import io.opentdf.platform.sdk.*; +import java.util.Collections; import java.util.concurrent.ExecutionException; -import io.opentdf.platform.authorization.*; - import java.util.List; public class GetEntitlements { @@ -23,7 +27,7 @@ public static void main(String[] args) throws ExecutionException, InterruptedExc .addEntities(Entity.newBuilder().setId("entity-1").setClientId("opentdf")) .build(); - GetEntitlementsResponse resp = sdk.getServices().authorization().getEntitlements(request).get(); + GetEntitlementsResponse resp = ResponseMessageKt.getOrThrow(sdk.getServices().authorization().getEntitlementsBlocking(request, Collections.emptyMap()).execute()); List entitlements = resp.getEntitlementsList(); diff --git a/examples/src/main/java/io/opentdf/platform/ListAttributes.java b/examples/src/main/java/io/opentdf/platform/ListAttributes.java index 366af20a..6d326e48 100644 --- a/examples/src/main/java/io/opentdf/platform/ListAttributes.java +++ b/examples/src/main/java/io/opentdf/platform/ListAttributes.java @@ -1,13 +1,13 @@ package io.opentdf.platform; +import com.connectrpc.ResponseMessageKt; +import io.opentdf.platform.generated.policy.Attribute; +import io.opentdf.platform.generated.policy.attributes.ListAttributesRequest; +import io.opentdf.platform.generated.policy.attributes.ListAttributesResponse; import io.opentdf.platform.sdk.*; +import java.util.Collections; import java.util.concurrent.ExecutionException; -import io.opentdf.platform.policy.AttributeRuleTypeEnum; - -import io.opentdf.platform.policy.attributes.*; -import io.opentdf.platform.policy.Attribute; - import java.util.List; public class ListAttributes { @@ -25,7 +25,7 @@ public static void main(String[] args) throws ExecutionException, InterruptedExc ListAttributesRequest request = ListAttributesRequest.newBuilder() .setNamespace("mynamespace.com").build(); - ListAttributesResponse resp = sdk.getServices().attributes().listAttributes(request).get(); + ListAttributesResponse resp = ResponseMessageKt.getOrThrow(sdk.getServices().attributes().listAttributesBlocking(request, Collections.emptyMap()).execute()); List attributes = resp.getAttributesList(); diff --git a/examples/src/main/java/io/opentdf/platform/ListNamespaces.java b/examples/src/main/java/io/opentdf/platform/ListNamespaces.java index 43e514a5..93e37d6b 100644 --- a/examples/src/main/java/io/opentdf/platform/ListNamespaces.java +++ b/examples/src/main/java/io/opentdf/platform/ListNamespaces.java @@ -1,11 +1,14 @@ package io.opentdf.platform; +import com.connectrpc.ResponseMessageKt; +import io.opentdf.platform.generated.policy.Namespace; +import io.opentdf.platform.generated.policy.namespaces.ListNamespacesRequest; +import io.opentdf.platform.generated.policy.namespaces.ListNamespacesResponse; import io.opentdf.platform.sdk.*; +import java.util.Collections; +import java.util.List; import java.util.concurrent.ExecutionException; -import io.opentdf.platform.policy.namespaces.*; -import io.opentdf.platform.policy.Namespace; - public class ListNamespaces { public static void main(String[] args) throws ExecutionException, InterruptedException{ @@ -20,8 +23,8 @@ public static void main(String[] args) throws ExecutionException, InterruptedExc ListNamespacesRequest request = ListNamespacesRequest.newBuilder().build(); - ListNamespacesResponse resp = sdk.getServices().namespaces().listNamespaces(request).get(); + ListNamespacesResponse resp = ResponseMessageKt.getOrThrow(sdk.getServices().namespaces().listNamespacesBlocking(request, Collections.emptyMap()).execute()); - java.util.List namespaces = resp.getNamespacesList(); + List namespaces = resp.getNamespacesList(); } } diff --git a/examples/src/main/java/io/opentdf/platform/ListSubjectMappings.java b/examples/src/main/java/io/opentdf/platform/ListSubjectMappings.java index 12bc4ab7..b92e0fca 100644 --- a/examples/src/main/java/io/opentdf/platform/ListSubjectMappings.java +++ b/examples/src/main/java/io/opentdf/platform/ListSubjectMappings.java @@ -1,11 +1,13 @@ package io.opentdf.platform; +import com.connectrpc.ResponseMessageKt; +import io.opentdf.platform.generated.policy.SubjectMapping; +import io.opentdf.platform.generated.policy.subjectmapping.ListSubjectMappingsRequest; +import io.opentdf.platform.generated.policy.subjectmapping.ListSubjectMappingsResponse; import io.opentdf.platform.sdk.*; +import java.util.Collections; import java.util.concurrent.ExecutionException; -import io.opentdf.platform.policy.subjectmapping.*; -import io.opentdf.platform.policy.SubjectMapping; - import java.util.List; public class ListSubjectMappings { @@ -22,7 +24,7 @@ public static void main(String[] args) throws ExecutionException, InterruptedExc ListSubjectMappingsRequest request = ListSubjectMappingsRequest.newBuilder().build(); - ListSubjectMappingsResponse resp = sdk.getServices().subjectMappings().listSubjectMappings(request).get(); + ListSubjectMappingsResponse resp = ResponseMessageKt.getOrThrow(sdk.getServices().subjectMappings().listSubjectMappingsBlocking(request, Collections.emptyMap()).execute()); List sms = resp.getSubjectMappingsList(); diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/SDK.java b/sdk/src/main/java/io/opentdf/platform/sdk/SDK.java index c441559d..3293e906 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/SDK.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/SDK.java @@ -2,11 +2,11 @@ import com.connectrpc.Interceptor; import com.connectrpc.impl.ProtocolClient; -import io.opentdf.platform.authorization.AuthorizationServiceClient; -import io.opentdf.platform.policy.attributes.AttributesServiceClient; -import io.opentdf.platform.policy.namespaces.NamespaceServiceClient; -import io.opentdf.platform.policy.resourcemapping.ResourceMappingServiceClient; -import io.opentdf.platform.policy.subjectmapping.SubjectMappingServiceClient; +import io.opentdf.platform.generated.authorization.AuthorizationServiceClient; +import io.opentdf.platform.generated.policy.attributes.AttributesServiceClient; +import io.opentdf.platform.generated.policy.namespaces.NamespaceServiceClient; +import io.opentdf.platform.generated.policy.resourcemapping.ResourceMappingServiceClient; +import io.opentdf.platform.generated.policy.subjectmapping.SubjectMappingServiceClient; import io.opentdf.platform.sdk.nanotdf.NanoTDFType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -65,7 +65,7 @@ public interface Services extends AutoCloseable { NamespaceServiceClient namespaces(); - SubjectMappingServiceClient subjectMappings(); + SubjectMappingServiceClient subjectMappings(); ResourceMappingServiceClient resourceMappings(); diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/AutoconfigureTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/AutoconfigureTest.java index ac7923a2..5c0abb6d 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/AutoconfigureTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/AutoconfigureTest.java @@ -13,23 +13,23 @@ import com.connectrpc.ResponseMessage; import com.connectrpc.UnaryBlockingCall; -import io.opentdf.platform.policy.Attribute; -import io.opentdf.platform.policy.Value; -import io.opentdf.platform.policy.attributes.AttributesServiceClient; -import io.opentdf.platform.policy.attributes.GetAttributeValuesByFqnsRequest; -import io.opentdf.platform.policy.attributes.GetAttributeValuesByFqnsResponse; +import io.opentdf.platform.generated.policy.Attribute; +import io.opentdf.platform.generated.policy.AttributeRuleTypeEnum; +import io.opentdf.platform.generated.policy.KasPublicKey; +import io.opentdf.platform.generated.policy.KasPublicKeyAlgEnum; +import io.opentdf.platform.generated.policy.KasPublicKeySet; +import io.opentdf.platform.generated.policy.KeyAccessServer; +import io.opentdf.platform.generated.policy.Namespace; +import io.opentdf.platform.generated.policy.PublicKey; +import io.opentdf.platform.generated.policy.Value; +import io.opentdf.platform.generated.policy.attributes.AttributesServiceClient; +import io.opentdf.platform.generated.policy.attributes.GetAttributeValuesByFqnsRequest; +import io.opentdf.platform.generated.policy.attributes.GetAttributeValuesByFqnsResponse; import io.opentdf.platform.sdk.Autoconfigure.AttributeValueFQN; import io.opentdf.platform.sdk.Autoconfigure.Granter.AttributeBooleanExpression; import io.opentdf.platform.sdk.Autoconfigure.Granter.BooleanKeyExpression; import io.opentdf.platform.sdk.Autoconfigure.KeySplitStep; import io.opentdf.platform.sdk.Autoconfigure.Granter; -import io.opentdf.platform.policy.Namespace; -import io.opentdf.platform.policy.PublicKey; -import io.opentdf.platform.policy.KeyAccessServer; -import io.opentdf.platform.policy.AttributeRuleTypeEnum; -import io.opentdf.platform.policy.KasPublicKey; -import io.opentdf.platform.policy.KasPublicKeyAlgEnum; -import io.opentdf.platform.policy.KasPublicKeySet; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/KASClientTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/KASClientTest.java index 9fe85bdd..e89f22e2 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/KASClientTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/KASClientTest.java @@ -16,12 +16,12 @@ import io.grpc.Server; import io.grpc.ServerBuilder; import io.grpc.stub.StreamObserver; -import io.opentdf.platform.kas.AccessServiceClient; -import io.opentdf.platform.kas.AccessServiceGrpc; -import io.opentdf.platform.kas.PublicKeyRequest; -import io.opentdf.platform.kas.PublicKeyResponse; -import io.opentdf.platform.kas.RewrapRequest; -import io.opentdf.platform.kas.RewrapResponse; +import io.opentdf.platform.generated.kas.AccessServiceClient; +import io.opentdf.platform.generated.kas.AccessServiceGrpc; +import io.opentdf.platform.generated.kas.PublicKeyRequest; +import io.opentdf.platform.generated.kas.PublicKeyResponse; +import io.opentdf.platform.generated.kas.RewrapRequest; +import io.opentdf.platform.generated.kas.RewrapResponse; import okhttp3.OkHttpClient; import okhttp3.Protocol; import org.junit.jupiter.api.Test; diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/SDKBuilderTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/SDKBuilderTest.java index fd192680..6570f27b 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/SDKBuilderTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/SDKBuilderTest.java @@ -11,15 +11,15 @@ import io.grpc.ServerInterceptor; import io.grpc.StatusRuntimeException; import io.grpc.stub.StreamObserver; -import io.opentdf.platform.kas.AccessServiceGrpc; -import io.opentdf.platform.kas.RewrapRequest; -import io.opentdf.platform.kas.RewrapResponse; -import io.opentdf.platform.policy.namespaces.GetNamespaceRequest; -import io.opentdf.platform.policy.namespaces.GetNamespaceResponse; -import io.opentdf.platform.policy.namespaces.NamespaceServiceGrpc; -import io.opentdf.platform.wellknownconfiguration.GetWellKnownConfigurationRequest; -import io.opentdf.platform.wellknownconfiguration.GetWellKnownConfigurationResponse; -import io.opentdf.platform.wellknownconfiguration.WellKnownServiceGrpc; +import io.opentdf.platform.generated.kas.AccessServiceGrpc; +import io.opentdf.platform.generated.kas.RewrapRequest; +import io.opentdf.platform.generated.kas.RewrapResponse; +import io.opentdf.platform.generated.policy.namespaces.GetNamespaceRequest; +import io.opentdf.platform.generated.policy.namespaces.GetNamespaceResponse; +import io.opentdf.platform.generated.policy.namespaces.NamespaceServiceGrpc; +import io.opentdf.platform.generated.wellknownconfiguration.GetWellKnownConfigurationRequest; +import io.opentdf.platform.generated.wellknownconfiguration.GetWellKnownConfigurationResponse; +import io.opentdf.platform.generated.wellknownconfiguration.WellKnownServiceGrpc; import nl.altindag.ssl.SSLFactory; import nl.altindag.ssl.pem.util.PemUtils; import nl.altindag.ssl.util.KeyStoreUtils; @@ -157,7 +157,7 @@ void sdkServicesSetup(boolean useSSLPlatform, boolean useSSLIDP) throws Exceptio WellKnownServiceGrpc.WellKnownServiceImplBase wellKnownService = new WellKnownServiceGrpc.WellKnownServiceImplBase() { @Override public void getWellKnownConfiguration(GetWellKnownConfigurationRequest request, - StreamObserver responseObserver) { + StreamObserver responseObserver) { var val = Value.newBuilder().setStringValue(issuer).build(); var config = Struct.newBuilder().putFields("platform_issuer", val).build(); var response = GetWellKnownConfigurationResponse @@ -189,7 +189,7 @@ public void getWellKnownConfiguration(GetWellKnownConfigurationRequest request, .addService(new NamespaceServiceGrpc.NamespaceServiceImplBase() { @Override public void getNamespace(GetNamespaceRequest request, - StreamObserver responseObserver) { + StreamObserver responseObserver) { var val = Value.newBuilder().setStringValue(issuer).build(); var response = GetNamespaceResponse .newBuilder() From 499c02f56425fe666a8d0498af9368dc8f20fa38 Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Mon, 21 Apr 2025 14:54:02 -0400 Subject: [PATCH 16/52] use variable --- sdk/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/pom.xml b/sdk/pom.xml index cfb8e0d0..db6794a5 100644 --- a/sdk/pom.xml +++ b/sdk/pom.xml @@ -229,7 +229,7 @@ com.squareup.okhttp3 okhttp-tls - 4.12.0 + ${okhttp.version} test From cb472ef9dc3c976571de088986646dbaf8b7e12b Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Mon, 21 Apr 2025 14:55:00 -0400 Subject: [PATCH 17/52] Update AutoconfigureTest.java --- .../io/opentdf/platform/sdk/AutoconfigureTest.java | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/AutoconfigureTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/AutoconfigureTest.java index 5c0abb6d..5cd62b16 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/AutoconfigureTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/AutoconfigureTest.java @@ -592,18 +592,6 @@ public void cancel() { } ); -// AttributesServiceGrpc.AttributesServiceFutureStub attributeGrpcStub = mock( -// AttributesServiceGrpc.AttributesServiceFutureStub.class); -// lenient().when(attributeGrpcStub.getAttributeValuesByFqns(any(GetAttributeValuesByFqnsRequest.class))) -// .thenAnswer( -// invocation -> { -// GetAttributeValuesByFqnsResponse resp = getResponse( -// (GetAttributeValuesByFqnsRequest) invocation.getArguments()[0]); -// SettableFuture future = SettableFuture.create(); -// future.set(resp); // Set the request as the future's result -// return future; -// }); - Granter reasoner = Autoconfigure.newGranterFromService(attributeService, new KASKeyCache(), tc.getPolicy().toArray(new AttributeValueFQN[0])); assertThat(reasoner).isNotNull(); From 4ef5636dbf19861ab95a2f48208fbc986d63cf2b Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Mon, 21 Apr 2025 15:51:32 -0400 Subject: [PATCH 18/52] normalize the platform endpoint --- .../io/opentdf/platform/sdk/KASClient.java | 38 +---------------- .../io/opentdf/platform/sdk/SDKBuilder.java | 41 +++++++++++++++++++ 2 files changed, 42 insertions(+), 37 deletions(-) diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java b/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java index 6e3b72ca..251d708b 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java @@ -23,10 +23,6 @@ import kotlin.collections.MapsKt; -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.time.Duration; @@ -128,38 +124,6 @@ public KASKeyCache getKeyCache() { return this.kasKeyCache; } - private String normalizeAddress(String urlString) { - URL url; - try { - url = new URL(urlString); - } catch (MalformedURLException e) { - url = tryParseHostAndPort(urlString); - } - final int port = url.getPort() == -1 ? ("http".equals(url.getProtocol()) ? 80 : 443 ) : url.getPort(); - final String protocol = usePlaintext && "http".equals(url.getProtocol()) ? "http" : "https"; - - try { - return new URL(protocol, url.getHost(), port, "").toString(); - } catch (MalformedURLException e) { - throw new SDKException("error creating KAS address", e); - } - } - - private URL tryParseHostAndPort(String urlString) { - URI uri; - try { - uri = new URI(null, urlString, null, null, null).parseServerAuthority(); - } catch (URISyntaxException e) { - throw new SDKException("error trying to parse host and port", e); - } - - try { - return new URL(uri.getPort() == 80 ? "http" : "https", uri.getHost(), uri.getPort(), ""); - } catch (MalformedURLException e) { - throw new SDKException("error trying to create URL from host and port", e); - } - } - @Override public synchronized void close() { } @@ -329,6 +293,6 @@ public byte[] unwrapNanoTDF(NanoTDFType.ECCurve curve, String header, String kas // make this protected so we can test the address normalization logic synchronized AccessServiceClient getStub(String url) { - return stubs.computeIfAbsent(normalizeAddress(url), channelFactory); + return stubs.computeIfAbsent(SDKBuilder.normalizeAddress(url, usePlaintext), channelFactory); } } diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java b/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java index e75184c3..f70277e4 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java @@ -44,6 +44,10 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; @@ -51,6 +55,8 @@ import java.util.UUID; import java.util.function.Function; +import static io.opentdf.platform.sdk.TDF.logger; + /** * A builder class for creating instances of the SDK class. */ @@ -74,6 +80,40 @@ public static SDKBuilder newBuilder() { return builder; } + static String normalizeAddress(String urlString, boolean usePlaintext) { + URL url; + try { + url = new URL(urlString); + } catch (MalformedURLException e) { + url = tryParseHostAndPort(urlString); + } + final int port = url.getPort() == -1 ? ("http".equals(url.getProtocol()) ? 80 : 443 ) : url.getPort(); + final String protocol = usePlaintext && "http".equals(url.getProtocol()) ? "http" : "https"; + + try { + var returnUrl = new URL(protocol, url.getHost(), port, "").toString(); + TDF.logger.debug("normalized url [{}] to [{}]", urlString, returnUrl); + return returnUrl; + } catch (MalformedURLException e) { + throw new SDKException("error creating KAS address", e); + } + } + + private static URL tryParseHostAndPort(String urlString) { + URI uri; + try { + uri = new URI(null, urlString, null, null, null).parseServerAuthority(); + } catch (URISyntaxException e) { + throw new SDKException("error trying to parse host and port", e); + } + + try { + return new URL(uri.getPort() == 80 ? "http" : "https", uri.getHost(), uri.getPort(), ""); + } catch (MalformedURLException e) { + throw new SDKException("error trying to create URL from host and port", e); + } + } + public SDKBuilder sslFactory(SSLFactory sslFactory) { this.sslFactory = sslFactory; return this; @@ -237,6 +277,7 @@ ServicesAndInternals buildServices() { throw new SDKException("Error generating DPoP key", e); } + this.platformEndpoint = normalizeAddress(this.platformEndpoint, this.usePlainText); var authInterceptor = getAuthInterceptor(dpopKey); var kasClient = getKASClient(dpopKey, authInterceptor); var protocolClient = getProtocolClient(platformEndpoint, authInterceptor); From 37da315476a4c1ca5eee160d23d25b67f7da4e10 Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Mon, 21 Apr 2025 16:07:54 -0400 Subject: [PATCH 19/52] change address normalization logic --- sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java | 6 +++--- .../test/java/io/opentdf/platform/sdk/KASClientTest.java | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java b/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java index f70277e4..04a86c52 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java @@ -87,12 +87,12 @@ static String normalizeAddress(String urlString, boolean usePlaintext) { } catch (MalformedURLException e) { url = tryParseHostAndPort(urlString); } - final int port = url.getPort() == -1 ? ("http".equals(url.getProtocol()) ? 80 : 443 ) : url.getPort(); + final int port = url.getPort() == -1 ? ("http".equals(url.getProtocol()) ? 80 : 443) : url.getPort(); final String protocol = usePlaintext && "http".equals(url.getProtocol()) ? "http" : "https"; try { var returnUrl = new URL(protocol, url.getHost(), port, "").toString(); - TDF.logger.debug("normalized url [{}] to [{}]", urlString, returnUrl); + logger.debug("normalized url [{}] to [{}]", urlString, returnUrl); return returnUrl; } catch (MalformedURLException e) { throw new SDKException("error creating KAS address", e); @@ -108,7 +108,7 @@ private static URL tryParseHostAndPort(String urlString) { } try { - return new URL(uri.getPort() == 80 ? "http" : "https", uri.getHost(), uri.getPort(), ""); + return new URL(uri.getPort() == 443 ? "https" : "http", uri.getHost(), uri.getPort(), ""); } catch (MalformedURLException e) { throw new SDKException("error trying to create URL from host and port", e); } diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/KASClientTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/KASClientTest.java index e89f22e2..a35f4570 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/KASClientTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/KASClientTest.java @@ -237,9 +237,9 @@ public void testAddressNormalizationWithInsecureHTTPClient() { assertThat(lastAddress.get()).isEqualTo("http://example.org:80"); assertThat(c1).isSameAs(c2); + // default to HTTP if no scheme is provided httpsKASClient.getStub("example.org:1234"); - assertThat(lastAddress.get()).isEqualTo("https://example.org:1234"); - + assertThat(lastAddress.get()).isEqualTo("http://example.org:1234"); } private static Server startServer(AccessServiceGrpc.AccessServiceImplBase accessService) throws IOException { From a9a8c9df53a10d04f22334a73e2c856026635ee6 Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Mon, 21 Apr 2025 16:46:56 -0400 Subject: [PATCH 20/52] show a more intentional message --- sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java b/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java index 04a86c52..b66dc72b 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java @@ -34,11 +34,13 @@ import nl.altindag.ssl.pem.util.PemUtils; import okhttp3.OkHttpClient; import okhttp3.Protocol; +import okhttp3.internal.platform.Platform; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.Nonnull; import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509ExtendedTrustManager; import java.io.File; import java.io.FileInputStream; @@ -296,6 +298,9 @@ private KASClient getKASClient(RSAKey dpopKey, Interceptor interceptor) { c.protocols(List.of(Protocol.H2_PRIOR_KNOWLEDGE)); } if (sslFactory != null) { + if (sslFactory.getTrustManager().isEmpty()) { + throw new SDKException("SSL factory must have a trust manager"); + } c.sslSocketFactory(sslFactory.getSslSocketFactory(), sslFactory.getTrustManager().get()); } var as = new ProtocolClient( From 503e9dde5f7b5b1ed4cbd6724b4830836fe052a7 Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Mon, 21 Apr 2025 16:57:49 -0400 Subject: [PATCH 21/52] consolidate factory method --- .../io/opentdf/platform/sdk/SDKBuilder.java | 29 +++---------------- 1 file changed, 4 insertions(+), 25 deletions(-) diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java b/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java index b66dc72b..a6459bcf 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java @@ -292,30 +292,7 @@ ServicesAndInternals buildServices() { @Nonnull private KASClient getKASClient(RSAKey dpopKey, Interceptor interceptor) { - Function clientFactory = (String endpoint) -> { - var c = new OkHttpClient.Builder(); - if (usePlainText) { - c.protocols(List.of(Protocol.H2_PRIOR_KNOWLEDGE)); - } - if (sslFactory != null) { - if (sslFactory.getTrustManager().isEmpty()) { - throw new SDKException("SSL factory must have a trust manager"); - } - c.sslSocketFactory(sslFactory.getSslSocketFactory(), sslFactory.getTrustManager().get()); - } - var as = new ProtocolClient( - new ConnectOkHttpClient(c.build()), - new ProtocolClientConfig(endpoint, - new GoogleJavaProtobufStrategy(), - NetworkProtocol.GRPC, - null, - GETConfiguration.Enabled.INSTANCE, - List.of(_ignored -> interceptor) - ) - ); - return new AccessServiceClient(as); - }; - return new KASClient(clientFactory, dpopKey, usePlainText); + return new KASClient((String endpoint) -> new AccessServiceClient(getProtocolClient(endpoint, interceptor)), dpopKey, usePlainText); } public SDK build() { @@ -333,9 +310,11 @@ private ProtocolClient getProtocolClient(String endpoint, Interceptor intercepto httpClient.protocols(List.of(Protocol.H2_PRIOR_KNOWLEDGE)); } if (sslFactory != null) { + if (sslFactory.getTrustManager().isEmpty()) { + throw new SDKException("SSL factory must have a trust manager"); + } httpClient.sslSocketFactory(sslFactory.getSslSocketFactory(), sslFactory.getTrustManager().get()); } - var protocolClientConfig = new ProtocolClientConfig( endpoint, new GoogleJavaProtobufStrategy(), From f33efd74f9d34815833061b49ffaed7d18476fec Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Mon, 21 Apr 2025 16:58:58 -0400 Subject: [PATCH 22/52] rename --- .../io/opentdf/platform/sdk/SDKBuilder.java | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java b/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java index a6459bcf..1f32f1b8 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java @@ -34,13 +34,11 @@ import nl.altindag.ssl.pem.util.PemUtils; import okhttp3.OkHttpClient; import okhttp3.Protocol; -import okhttp3.internal.platform.Platform; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.Nonnull; import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509ExtendedTrustManager; import java.io.File; import java.io.FileInputStream; @@ -55,9 +53,6 @@ import java.util.Collections; import java.util.List; import java.util.UUID; -import java.util.function.Function; - -import static io.opentdf.platform.sdk.TDF.logger; /** * A builder class for creating instances of the SDK class. @@ -214,7 +209,7 @@ private Interceptor getAuthInterceptor(RSAKey rsaKey) { // well known endpoint ProtocolClient bootstrapClient = null; GetWellKnownConfigurationResponse config; - bootstrapClient = getProtocolClient(platformEndpoint) ; + bootstrapClient = getUnauthenticatedProtocolClient(platformEndpoint) ; var stub = new WellKnownServiceClient(bootstrapClient); try { config = ResponseMessageKt.getOrThrow(stub.getWellKnownConfigurationBlocking(GetWellKnownConfigurationRequest.getDefaultInstance(), Collections.emptyMap()).execute()); @@ -282,7 +277,7 @@ ServicesAndInternals buildServices() { this.platformEndpoint = normalizeAddress(this.platformEndpoint, this.usePlainText); var authInterceptor = getAuthInterceptor(dpopKey); var kasClient = getKASClient(dpopKey, authInterceptor); - var protocolClient = getProtocolClient(platformEndpoint, authInterceptor); + var protocolClient = getUnauthenticatedProtocolClient(platformEndpoint, authInterceptor); return new ServicesAndInternals( authInterceptor, @@ -292,7 +287,7 @@ ServicesAndInternals buildServices() { @Nonnull private KASClient getKASClient(RSAKey dpopKey, Interceptor interceptor) { - return new KASClient((String endpoint) -> new AccessServiceClient(getProtocolClient(endpoint, interceptor)), dpopKey, usePlainText); + return new KASClient((String endpoint) -> new AccessServiceClient(getUnauthenticatedProtocolClient(endpoint, interceptor)), dpopKey, usePlainText); } public SDK build() { @@ -300,11 +295,11 @@ public SDK build() { return new SDK(services.services, services.trustManager, services.interceptor); } - private ProtocolClient getProtocolClient(String endpoint) { - return getProtocolClient(endpoint, null); + private ProtocolClient getUnauthenticatedProtocolClient(String endpoint) { + return getUnauthenticatedProtocolClient(endpoint, null); } - private ProtocolClient getProtocolClient(String endpoint, Interceptor interceptor) { + private ProtocolClient getUnauthenticatedProtocolClient(String endpoint, Interceptor authInterceptor) { var httpClient = new OkHttpClient.Builder(); if (usePlainText) { httpClient.protocols(List.of(Protocol.H2_PRIOR_KNOWLEDGE)); @@ -321,7 +316,7 @@ private ProtocolClient getProtocolClient(String endpoint, Interceptor intercepto NetworkProtocol.GRPC, null, GETConfiguration.Enabled.INSTANCE, - interceptor == null ? Collections.emptyList() : List.of((_config) -> interceptor) + authInterceptor == null ? Collections.emptyList() : List.of((_config) -> authInterceptor) ); return new ProtocolClient(new ConnectOkHttpClient(httpClient.build()), protocolClientConfig); From a69f7951272518545a3ab7d96cd98d75f7279d89 Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Mon, 21 Apr 2025 17:40:12 -0400 Subject: [PATCH 23/52] use the API right --- .../platform/sdk/AutoconfigureTest.java | 102 ++++++++---------- 1 file changed, 45 insertions(+), 57 deletions(-) diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/AutoconfigureTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/AutoconfigureTest.java index 5cd62b16..38f6d52f 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/AutoconfigureTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/AutoconfigureTest.java @@ -576,21 +576,20 @@ public void testReasonerSpecificity() { for (ReasonerTestCase tc : testCases) { var attributeService = mock(AttributesServiceClient.class); - when(attributeService.getAttributeValuesByFqnsBlocking(any(), any())).thenReturn( - new UnaryBlockingCall<>() { - @Override - public ResponseMessage execute() { - return new ResponseMessage.Success<>( - getResponse(GetAttributeValuesByFqnsRequest.newBuilder() - .addAllFqns(tc.getPolicy().stream().map(AttributeValueFQN::toString).collect(Collectors.toList())) - .build()), Collections.emptyMap(), Collections.emptyMap()); - } - - @Override - public void cancel() { - } + when(attributeService.getAttributeValuesByFqnsBlocking(any(), any())).thenAnswer(invocation -> { + var request = (GetAttributeValuesByFqnsRequest) invocation.getArgument(0); + return new UnaryBlockingCall() { + @Override + public ResponseMessage execute() { + return new ResponseMessage.Success<>(getResponse(request), Collections.emptyMap(), Collections.emptyMap()); } - ); + + @Override + public void cancel() { + } + }; + }); + Granter reasoner = Autoconfigure.newGranterFromService(attributeService, new KASKeyCache(), tc.getPolicy().toArray(new AttributeValueFQN[0])); @@ -900,49 +899,38 @@ void testKeyCacheFromGrants() throws InterruptedException, ExecutionException { .setUri("https://example.com/kas") .build(); -// AttributesServiceGrpc.AttributesServiceFutureStub attributeGrpcStub = mock( -// AttributesServiceGrpc.AttributesServiceFutureStub.class); -// lenient().when(attributeGrpcStub.getAttributeValuesByFqns(any(GetAttributeValuesByFqnsRequest.class))) -// .thenAnswer( -// invocation -> { -// GetAttributeValuesByFqnsResponse resp = getResponseWithGrants( -// (GetAttributeValuesByFqnsRequest) invocation.getArguments()[0], List.of(kas1)); -// SettableFuture future = SettableFuture.create(); -// future.set(resp); // Set the request as the future's result -// return future; -// }); -// AttributesServiceClient attributeGrpcStub = mock(AttributesServiceClient.class); -// lenient().when(attributeGrpcStub.getAttributeValuesByFqns(any(), any())) -// .thenAnswer( -// invocation -> { -// GetAttributeValuesByFqnsResponse resp = getResponseWithGrants( -// (GetAttributeValuesByFqnsRequest) invocation.getArguments()[0], List.of(kas1)); -// SettableFuture future = SettableFuture.create(); -// future.set(resp); // Set the request as the future's result -// return future; -// }); - -// KASKeyCache keyCache = new KASKeyCache(); -// -// Granter reasoner = Autoconfigure.newGranterFromService(attributeGrpcStub, keyCache, -// List.of(clsS, rel2gbr, rel2usa, n2kHCS, n2kSI).toArray(new AttributeValueFQN[0])); -// assertThat(reasoner).isNotNull(); -// -// // Verify that the key was stored in the cache -// Config.KASInfo storedKASInfo = keyCache.get("https://example.com/kas", "ec:secp256r1"); -// assertNotNull(storedKASInfo); -// assertEquals("https://example.com/kas", storedKASInfo.URL); -// assertEquals("test-kid", storedKASInfo.KID); -// assertEquals("ec:secp256r1", storedKASInfo.Algorithm); -// assertEquals("public-key-pem", storedKASInfo.PublicKey); -// -// Config.KASInfo storedKASInfo2 = keyCache.get("https://example.com/kas", "rsa:2048"); -// assertNotNull(storedKASInfo2); -// assertEquals("https://example.com/kas", storedKASInfo2.URL); -// assertEquals("test-kid-2", storedKASInfo2.KID); -// assertEquals("rsa:2048", storedKASInfo2.Algorithm); -// assertEquals("public-key-pem-2", storedKASInfo2.PublicKey); -// + AttributesServiceClient attributesServiceClient = mock(AttributesServiceClient.class); + when(attributesServiceClient.getAttributeValuesByFqnsBlocking(any(), any())).thenAnswer(invocation -> { + var request = (GetAttributeValuesByFqnsRequest)invocation.getArgument(0); + return new UnaryBlockingCall(){ + @Override + public ResponseMessage execute() { + return new ResponseMessage.Success<>(getResponseWithGrants(request, List.of(kas1)), Collections.emptyMap(), Collections.emptyMap()); + } + @Override public void cancel() {} + }; + }); + + KASKeyCache keyCache = new KASKeyCache(); + + Granter reasoner = Autoconfigure.newGranterFromService(attributesServiceClient, keyCache, + List.of(clsS, rel2gbr, rel2usa, n2kHCS, n2kSI).toArray(new AttributeValueFQN[0])); + assertThat(reasoner).isNotNull(); + + // Verify that the key was stored in the cache + Config.KASInfo storedKASInfo = keyCache.get("https://example.com/kas", "ec:secp256r1"); + assertNotNull(storedKASInfo); + assertEquals("https://example.com/kas", storedKASInfo.URL); + assertEquals("test-kid", storedKASInfo.KID); + assertEquals("ec:secp256r1", storedKASInfo.Algorithm); + assertEquals("public-key-pem", storedKASInfo.PublicKey); + + Config.KASInfo storedKASInfo2 = keyCache.get("https://example.com/kas", "rsa:2048"); + assertNotNull(storedKASInfo2); + assertEquals("https://example.com/kas", storedKASInfo2.URL); + assertEquals("test-kid-2", storedKASInfo2.KID); + assertEquals("rsa:2048", storedKASInfo2.Algorithm); + assertEquals("public-key-pem-2", storedKASInfo2.PublicKey); } } From 7e13a5fab202986c071cadfc232ee1a3c3a199a8 Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Tue, 22 Apr 2025 09:39:50 -0400 Subject: [PATCH 24/52] comment --- sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java | 1 + 1 file changed, 1 insertion(+) diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java b/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java index 1f32f1b8..467630a0 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java @@ -302,6 +302,7 @@ private ProtocolClient getUnauthenticatedProtocolClient(String endpoint) { private ProtocolClient getUnauthenticatedProtocolClient(String endpoint, Interceptor authInterceptor) { var httpClient = new OkHttpClient.Builder(); if (usePlainText) { + // we can only connect using HTTP/2 without any negotiation when using plain test httpClient.protocols(List.of(Protocol.H2_PRIOR_KNOWLEDGE)); } if (sslFactory != null) { From 78442f2a48e7bd151dd839312f9c425e0903d78d Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Tue, 22 Apr 2025 10:01:33 -0400 Subject: [PATCH 25/52] cleanup --- sdk/pom.xml | 61 ++++++++++--------- .../io/opentdf/platform/sdk/KASClient.java | 2 +- .../java/io/opentdf/platform/sdk/SDK.java | 2 - .../io/opentdf/platform/sdk/SDKBuilder.java | 40 +----------- .../io/opentdf/platform/sdk/TokenSource.java | 13 +--- .../opentdf/platform/sdk/KASClientTest.java | 20 ------ 6 files changed, 35 insertions(+), 103 deletions(-) diff --git a/sdk/pom.xml b/sdk/pom.xml index db6794a5..1e20ddbd 100644 --- a/sdk/pom.xml +++ b/sdk/pom.xml @@ -288,36 +288,6 @@ - - kotlin-maven-plugin - - - compile - compile - - compile - - - - src/main/kotlin - target/generated-sources - - - - - test-compile - test-compile - - test-compile - - - - - 1.8 - - org.jetbrains.kotlin - ${kotlin.version} - org.apache.maven.plugins @@ -465,6 +435,37 @@ + kotlin-maven-plugin + + + compile + compile + + compile + + + + src/main/kotlin + target/generated-sources + + + + + test-compile + test-compile + + test-compile + + + + + 1.8 + + org.jetbrains.kotlin + ${kotlin.version} + + + org.apache.maven.plugins maven-compiler-plugin diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java b/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java index 251d708b..e703e5ea 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java @@ -293,6 +293,6 @@ public byte[] unwrapNanoTDF(NanoTDFType.ECCurve curve, String header, String kas // make this protected so we can test the address normalization logic synchronized AccessServiceClient getStub(String url) { - return stubs.computeIfAbsent(SDKBuilder.normalizeAddress(url, usePlaintext), channelFactory); + return stubs.computeIfAbsent(AddressNormalizer.normalizeAddress(url, usePlaintext), channelFactory); } } diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/SDK.java b/sdk/src/main/java/io/opentdf/platform/sdk/SDK.java index 3293e906..77447f65 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/SDK.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/SDK.java @@ -27,8 +27,6 @@ public class SDK implements AutoCloseable { private final TrustManager trustManager; private final Interceptor authInterceptor; - private static final Logger log = LoggerFactory.getLogger(SDK.class); - /** * Closes the SDK, including its associated services. * diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java b/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java index 467630a0..abbea932 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java @@ -44,10 +44,6 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; @@ -77,40 +73,6 @@ public static SDKBuilder newBuilder() { return builder; } - static String normalizeAddress(String urlString, boolean usePlaintext) { - URL url; - try { - url = new URL(urlString); - } catch (MalformedURLException e) { - url = tryParseHostAndPort(urlString); - } - final int port = url.getPort() == -1 ? ("http".equals(url.getProtocol()) ? 80 : 443) : url.getPort(); - final String protocol = usePlaintext && "http".equals(url.getProtocol()) ? "http" : "https"; - - try { - var returnUrl = new URL(protocol, url.getHost(), port, "").toString(); - logger.debug("normalized url [{}] to [{}]", urlString, returnUrl); - return returnUrl; - } catch (MalformedURLException e) { - throw new SDKException("error creating KAS address", e); - } - } - - private static URL tryParseHostAndPort(String urlString) { - URI uri; - try { - uri = new URI(null, urlString, null, null, null).parseServerAuthority(); - } catch (URISyntaxException e) { - throw new SDKException("error trying to parse host and port", e); - } - - try { - return new URL(uri.getPort() == 443 ? "https" : "http", uri.getHost(), uri.getPort(), ""); - } catch (MalformedURLException e) { - throw new SDKException("error trying to create URL from host and port", e); - } - } - public SDKBuilder sslFactory(SSLFactory sslFactory) { this.sslFactory = sslFactory; return this; @@ -274,7 +236,7 @@ ServicesAndInternals buildServices() { throw new SDKException("Error generating DPoP key", e); } - this.platformEndpoint = normalizeAddress(this.platformEndpoint, this.usePlainText); + this.platformEndpoint = AddressNormalizer.normalizeAddress(this.platformEndpoint, this.usePlainText); var authInterceptor = getAuthInterceptor(dpopKey); var kasClient = getKASClient(dpopKey, authInterceptor); var protocolClient = getUnauthenticatedProtocolClient(platformEndpoint, authInterceptor); diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/TokenSource.java b/sdk/src/main/java/io/opentdf/platform/sdk/TokenSource.java index d08b55a7..0fbfa1b7 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/TokenSource.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/TokenSource.java @@ -1,6 +1,5 @@ package io.opentdf.platform.sdk; -import com.connectrpc.http.HTTPMethod; import com.nimbusds.jose.JOSEException; import com.nimbusds.jose.JWSAlgorithm; import com.nimbusds.jose.jwk.RSAKey; @@ -15,13 +14,6 @@ import com.nimbusds.oauth2.sdk.http.HTTPRequest; import com.nimbusds.oauth2.sdk.http.HTTPResponse; import com.nimbusds.oauth2.sdk.token.AccessToken; -import io.grpc.CallOptions; -import io.grpc.Channel; -import io.grpc.ClientCall; -import io.grpc.ClientInterceptor; -import io.grpc.ForwardingClientCall; -import io.grpc.Metadata; -import io.grpc.MethodDescriptor; import nl.altindag.ssl.SSLFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -32,9 +24,8 @@ import java.time.Instant; /** - * The GRPCAuthInterceptor class is responsible for intercepting client calls before they are sent - * to the server. It adds authentication headers to the requests by fetching and caching access - * tokens. + * The TokenSource class is responsible for providing authorization tokens. It handles + * timeouts and creating OIDC calls. It is thread-safe. */ class TokenSource { private Instant tokenExpiryTime; diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/KASClientTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/KASClientTest.java index a35f4570..8412b428 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/KASClientTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/KASClientTest.java @@ -202,15 +202,6 @@ public void testAddressNormalizationWithHTTPSClient() { var otherStub = httpsKASClient.getStub("https://localhost:8080"); assertThat(lastAddress.get()).isEqualTo("https://localhost:8080"); assertThat(stub).isSameAs(otherStub); - - httpsKASClient.getStub("https://example.org"); - assertThat(lastAddress.get()).isEqualTo("https://example.org:443"); - - httpsKASClient.getStub("http://example.org"); - assertThat(lastAddress.get()).isEqualTo("https://example.org:80"); - - httpsKASClient.getStub("example.org:1234"); - assertThat(lastAddress.get()).isEqualTo("https://example.org:1234"); } @Test @@ -224,22 +215,11 @@ public void testAddressNormalizationWithInsecureHTTPClient() { return aclientFactory.apply(addr); }, dpopKey, true); - httpsKASClient.getStub("http://localhost:8080"); - assertThat(lastAddress.get()).isEqualTo("http://localhost:8080"); - assertThat(lastAddress.get()).isEqualTo("http://localhost:8080"); - - httpsKASClient.getStub("https://example.org"); - assertThat(lastAddress.get()).isEqualTo("https://example.org:443"); - var c1 = httpsKASClient.getStub("http://example.org"); assertThat(lastAddress.get()).isEqualTo("http://example.org:80"); var c2 = httpsKASClient.getStub("example.org:80"); assertThat(lastAddress.get()).isEqualTo("http://example.org:80"); assertThat(c1).isSameAs(c2); - - // default to HTTP if no scheme is provided - httpsKASClient.getStub("example.org:1234"); - assertThat(lastAddress.get()).isEqualTo("http://example.org:1234"); } private static Server startServer(AccessServiceGrpc.AccessServiceImplBase accessService) throws IOException { From bc3d212754cd97bf28bf7e69bd1713e1c1f02b28 Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Tue, 22 Apr 2025 10:10:56 -0400 Subject: [PATCH 26/52] cleanup --- .../io/opentdf/platform/sdk/KASClient.java | 53 +++++++++---------- .../io/opentdf/platform/sdk/SDKBuilder.java | 2 +- .../java/io/opentdf/platform/sdk/TDF.java | 3 +- .../io/opentdf/platform/sdk/TokenSource.java | 5 +- 4 files changed, 29 insertions(+), 34 deletions(-) diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java b/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java index e703e5ea..b2d01290 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java @@ -152,7 +152,7 @@ static class NanoTDFRewrapRequestBody { @Override public byte[] unwrap(Manifest.KeyAccess keyAccess, String policy, KeyType sessionKeyType) { ECKeyPair ecKeyPair = null; - + if (sessionKeyType.isEc()) { var curveName = sessionKeyType.getCurveName(); ecKeyPair = new ECKeyPair(curveName, ECKeyPair.ECAlgorithm.ECDH); @@ -191,40 +191,37 @@ public byte[] unwrap(Manifest.KeyAccess keyAccess, String policy, KeyType sessi .setSignedRequestToken(jwt.serialize()) .build(); RewrapResponse response; + var req = getStub(keyAccess.url).rewrapBlocking(request, Collections.emptyMap()).execute(); try { - var req = getStub(keyAccess.url).rewrapBlocking(request, Collections.emptyMap()).execute(); - try { - response = getOrThrow(req); - } catch (Exception e) { - throw new SDKException("error unwrapping key", e); - } - var wrappedKey = response.getEntityWrappedKey().toByteArray(); - if (sessionKeyType != KeyType.RSA2048Key) { - - if (ecKeyPair == null) { - throw new SDKException("ECKeyPair is null. Unable to proceed with the unwrap operation."); - } - - var kasEphemeralPublicKey = response.getSessionPublicKey(); - var publicKey = ECKeyPair.publicKeyFromPem(kasEphemeralPublicKey); - byte[] symKey = ECKeyPair.computeECDHKey(publicKey, ecKeyPair.getPrivateKey()); - - var sessionKey = ECKeyPair.calculateHKDF(GLOBAL_KEY_SALT, symKey); - - AesGcm gcm = new AesGcm(sessionKey); - AesGcm.Encrypted encrypted = new AesGcm.Encrypted(wrappedKey); - return gcm.decrypt(encrypted); - } else { - return decryptor.decrypt(wrappedKey); - } + response = getOrThrow(req); } catch (StatusRuntimeException e) { if (e.getStatus().getCode() == Status.Code.INVALID_ARGUMENT) { // 400 Bad Request - throw new KasBadRequestException("rewrap request 400: " + e.toString()); + throw new KasBadRequestException("rewrap request 400: " + e); } throw e; + } catch (Exception e) { + throw new SDKException("error unwrapping key", e); + } + var wrappedKey = response.getEntityWrappedKey().toByteArray(); + if (sessionKeyType != KeyType.RSA2048Key) { + + if (ecKeyPair == null) { + throw new SDKException("ECKeyPair is null. Unable to proceed with the unwrap operation."); + } + + var kasEphemeralPublicKey = response.getSessionPublicKey(); + var publicKey = ECKeyPair.publicKeyFromPem(kasEphemeralPublicKey); + byte[] symKey = ECKeyPair.computeECDHKey(publicKey, ecKeyPair.getPrivateKey()); + + var sessionKey = ECKeyPair.calculateHKDF(GLOBAL_KEY_SALT, symKey); + + AesGcm gcm = new AesGcm(sessionKey); + AesGcm.Encrypted encrypted = new AesGcm.Encrypted(wrappedKey); + return gcm.decrypt(encrypted); + } else { + return decryptor.decrypt(wrappedKey); } - } public byte[] unwrapNanoTDF(NanoTDFType.ECCurve curve, String header, String kasURL) { diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java b/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java index abbea932..5247f074 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java @@ -279,7 +279,7 @@ private ProtocolClient getUnauthenticatedProtocolClient(String endpoint, Interce NetworkProtocol.GRPC, null, GETConfiguration.Enabled.INSTANCE, - authInterceptor == null ? Collections.emptyList() : List.of((_config) -> authInterceptor) + authInterceptor == null ? Collections.emptyList() : List.of(_config -> authInterceptor) ); return new ProtocolClient(new ConnectOkHttpClient(httpClient.build()), protocolClientConfig); diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java b/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java index fe92476c..2ebe8634 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java @@ -27,7 +27,6 @@ import java.security.*; import java.text.ParseException; import java.util.*; -import java.util.concurrent.ExecutionException; /** * The TDF class is responsible for handling operations related to @@ -476,7 +475,7 @@ private static byte[] calculateSignature(byte[] data, byte[] secret, Config.Inte public TDFObject createTDF(InputStream payload, OutputStream outputStream, Config.TDFConfig tdfConfig, SDK.KAS kas, AttributesServiceClient attributesServiceClient) - throws IOException, JOSEException, AutoConfigureException, InterruptedException, ExecutionException, DecoderException { + throws IOException, JOSEException, AutoConfigureException, DecoderException { if (tdfConfig.autoconfigure) { Autoconfigure.Granter granter = new Autoconfigure.Granter(new ArrayList<>()); diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/TokenSource.java b/sdk/src/main/java/io/opentdf/platform/sdk/TokenSource.java index 0fbfa1b7..8a4c2f71 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/TokenSource.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/TokenSource.java @@ -82,9 +82,9 @@ public AuthHeaders getAuthHeaders(URL url, String method) { SignedJWT proof = dpopFactory.createDPoPJWT(method, url.toURI(), t); dpopProof = proof.serialize(); } catch (URISyntaxException e) { - throw new RuntimeException("Invalid URI syntax for DPoP proof creation", e); + throw new SDKException("Invalid URI syntax for DPoP proof creation", e); } catch (JOSEException e) { - throw new RuntimeException("Error creating DPoP proof", e); + throw new SDKException("Error creating DPoP proof", e); } return new AuthHeaders( @@ -127,7 +127,6 @@ private synchronized AccessToken getToken() { throw new RuntimeException("Token request failed: " + error); } - var tokens = tokenResponse.toSuccessResponse().getTokens(); if (tokens.getDPoPAccessToken() != null) { logger.trace("retrieved a new DPoP access token"); From 931a64fc151a7cbc07cf98c5f52fec573fa73651 Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Tue, 22 Apr 2025 12:17:12 -0400 Subject: [PATCH 27/52] cleanup --- keys/kas-cert.pem | 19 +++++++ .../platform/sdk/AddressNormalizer.java | 52 +++++++++++++++++++ .../io/opentdf/platform/sdk/SDKBuilder.java | 4 +- .../platform/sdk/AddressNormalizerTest.java | 25 +++++++++ 4 files changed, 98 insertions(+), 2 deletions(-) create mode 100644 keys/kas-cert.pem create mode 100644 sdk/src/main/java/io/opentdf/platform/sdk/AddressNormalizer.java create mode 100644 sdk/src/test/java/io/opentdf/platform/sdk/AddressNormalizerTest.java diff --git a/keys/kas-cert.pem b/keys/kas-cert.pem new file mode 100644 index 00000000..2e8f6bb0 --- /dev/null +++ b/keys/kas-cert.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIC/TCCAeWgAwIBAgIUUCu5Zu5b8PIRhAFJ5VpaZFP35HQwDQYJKoZIhvcNAQEL +BQAwDjEMMAoGA1UEAwwDa2FzMB4XDTI0MDgxNTExMzYxN1oXDTI1MDgxNTExMzYx +N1owDjEMMAoGA1UEAwwDa2FzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEAx4PgRz+H/U4X5hf3Uj2JgqqyG7fX0QDSoxI3Ziu1Q+gbxElVppOqJQMA88Yd +8uug2Px/vtmkFwGXGi5DzKq6bLcpCJEXc1TOtJH5o+2Wcu1Ahfp9MxkOXpbJvoH8 +JRkjOp6ZPIiIM/IOQDq3eHpLVAB6ihDFDwzhJsqBMVMejmkDRNj8qx5AkZSrE4zi +AL7hV/TWWyCiq8rLiWVnZOFXNHyRtPmTgmerRg5Ad1lP9muMrLJ/1ziq1lILk7fB +a31yOmS3g25MGYYwX+7PCNMWkhX0eCLAyosYfIp/K0SOJ3WO9G4eiq9keb4xRSbB +jFKmadNBITEWIhPzCAzT8nDlFwIDAQABo1MwUTAdBgNVHQ4EFgQUFc94TI8PUU+u +uASVIyQgQm4tHRswHwYDVR0jBBgwFoAUFc94TI8PUU+uuASVIyQgQm4tHRswDwYD +VR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAjvzUeU9otYOM0UpwhZ/j +nBxNcP0J7fAODokL1as8zXnv7t8mk327b8HSORkfGdbCsf5be10W9xGK1DoRhscA +3wkASiWOk2wsIq9l12tR247EJXk7VMcDXQfGuVhzMhN1gp25bxX2FsmKvxnOFuN2 +Qv4BY61dQSHjoDQDRhYb9naYTqsppiHjj11nayfEY6nVivs9Hu9jXqakcE4wSksX +DRRgxAs2KBcbQ0/rfOZs7yPs8jlqpmPk09M+yV7Tn1943EaAnWyiuavW7g5Zn/dM +szuJrlIzmgRdUyHTD4tS6ebTqGo+hziYNdfHUdjQF8JDMiRFwx/xoqC5/1jP18kX +MQ== +-----END CERTIFICATE----- diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/AddressNormalizer.java b/sdk/src/main/java/io/opentdf/platform/sdk/AddressNormalizer.java new file mode 100644 index 00000000..6ad6d59f --- /dev/null +++ b/sdk/src/main/java/io/opentdf/platform/sdk/AddressNormalizer.java @@ -0,0 +1,52 @@ +package io.opentdf.platform.sdk; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; + +class AddressNormalizer { + private static final Logger logger = LoggerFactory.getLogger(AddressNormalizer.class); + + static String normalizeAddress(String urlString, boolean usePlaintext) { + URL url; + try { + url = new URL(urlString); + } catch (MalformedURLException e) { + url = tryParseHostAndPort(urlString); + } + final int port; + if (url.getPort() == -1) { + port = "http".equals(url.getProtocol()) ? 80 : 443; + } else { + port = url.getPort(); + } + final String protocol = usePlaintext && "http".equals(url.getProtocol()) ? "http" : "https"; + + try { + var returnUrl = new URL(protocol, url.getHost(), port, "").toString(); + logger.debug("normalized url [{}] to [{}]", urlString, returnUrl); + return returnUrl; + } catch (MalformedURLException e) { + throw new SDKException("error creating KAS address", e); + } + } + + private static URL tryParseHostAndPort(String urlString) { + URI uri; + try { + uri = new URI(null, urlString, null, null, null).parseServerAuthority(); + } catch (URISyntaxException e) { + throw new SDKException("error trying to parse host and port", e); + } + + try { + return new URL(uri.getPort() == 443 ? "https" : "http", uri.getHost(), uri.getPort(), ""); + } catch (MalformedURLException e) { + throw new SDKException("error trying to create URL from host and port", e); + } + } +} diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java b/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java index 5247f074..9710dca4 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java @@ -239,7 +239,7 @@ ServicesAndInternals buildServices() { this.platformEndpoint = AddressNormalizer.normalizeAddress(this.platformEndpoint, this.usePlainText); var authInterceptor = getAuthInterceptor(dpopKey); var kasClient = getKASClient(dpopKey, authInterceptor); - var protocolClient = getUnauthenticatedProtocolClient(platformEndpoint, authInterceptor); + var protocolClient = getProtocolClient(platformEndpoint, authInterceptor); return new ServicesAndInternals( authInterceptor, @@ -249,7 +249,7 @@ ServicesAndInternals buildServices() { @Nonnull private KASClient getKASClient(RSAKey dpopKey, Interceptor interceptor) { - return new KASClient((String endpoint) -> new AccessServiceClient(getUnauthenticatedProtocolClient(endpoint, interceptor)), dpopKey, usePlainText); + return new KASClient((String endpoint) -> new AccessServiceClient(getProtocolClient(endpoint, interceptor)), dpopKey, usePlainText); } public SDK build() { diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/AddressNormalizerTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/AddressNormalizerTest.java new file mode 100644 index 00000000..0786a7ac --- /dev/null +++ b/sdk/src/test/java/io/opentdf/platform/sdk/AddressNormalizerTest.java @@ -0,0 +1,25 @@ +package io.opentdf.platform.sdk; + + +import org.junit.jupiter.api.Test; + +import static io.opentdf.platform.sdk.AddressNormalizer.normalizeAddress; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +class AddressNormalizerTest { + + @Test + public void testAddressNormalizationWithHTTPSClient() { + assertThat(normalizeAddress("http://example.org", false)).isEqualTo("https://example.org:80"); + // default to https if no scheme is provided + assertThat(normalizeAddress("example.org:1234", false)).isEqualTo("https://example.org:1234"); + } + + @Test + public void testAddressNormaliationWithInsecureHTTPClient() { + assertThat(normalizeAddress("http://localhost:8080", true)).isEqualTo("http://localhost:8080"); + assertThat(normalizeAddress("https://example.org", true)).isEqualTo("https://example.org:443"); + // default to http if no scheme is provided + assertThat(normalizeAddress("example.org:1234", true)).isEqualTo("http://example.org:1234"); + } +} \ No newline at end of file From c4e34afa01a135238823052977cbff24cee5b8fd Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Tue, 22 Apr 2025 20:05:36 -0400 Subject: [PATCH 28/52] cleanup --- sdk/src/main/java/io/opentdf/platform/sdk/SDK.java | 2 -- sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java | 7 ++++--- .../java/io/opentdf/platform/sdk/AutoconfigureTest.java | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/SDK.java b/sdk/src/main/java/io/opentdf/platform/sdk/SDK.java index 77447f65..7e050dc5 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/SDK.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/SDK.java @@ -8,8 +8,6 @@ import io.opentdf.platform.generated.policy.resourcemapping.ResourceMappingServiceClient; import io.opentdf.platform.generated.policy.subjectmapping.SubjectMappingServiceClient; import io.opentdf.platform.sdk.nanotdf.NanoTDFType; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import javax.net.ssl.TrustManager; import java.io.IOException; diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java b/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java index 9710dca4..68852a2d 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java @@ -258,10 +258,10 @@ public SDK build() { } private ProtocolClient getUnauthenticatedProtocolClient(String endpoint) { - return getUnauthenticatedProtocolClient(endpoint, null); + return getProtocolClient(endpoint, null); } - private ProtocolClient getUnauthenticatedProtocolClient(String endpoint, Interceptor authInterceptor) { + private ProtocolClient getProtocolClient(String endpoint, Interceptor authInterceptor) { var httpClient = new OkHttpClient.Builder(); if (usePlainText) { // we can only connect using HTTP/2 without any negotiation when using plain test @@ -273,13 +273,14 @@ private ProtocolClient getUnauthenticatedProtocolClient(String endpoint, Interce } httpClient.sslSocketFactory(sslFactory.getSslSocketFactory(), sslFactory.getTrustManager().get()); } + var protocolClientConfig = new ProtocolClientConfig( endpoint, new GoogleJavaProtobufStrategy(), NetworkProtocol.GRPC, null, GETConfiguration.Enabled.INSTANCE, - authInterceptor == null ? Collections.emptyList() : List.of(_config -> authInterceptor) + authInterceptor == null ? Collections.emptyList() : List.of(ignoredConfig -> authInterceptor) ); return new ProtocolClient(new ConnectOkHttpClient(httpClient.build()), protocolClientConfig); diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/AutoconfigureTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/AutoconfigureTest.java index 38f6d52f..c744d055 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/AutoconfigureTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/AutoconfigureTest.java @@ -872,7 +872,7 @@ GetAttributeValuesByFqnsResponse getResponseWithGrants(GetAttributeValuesByFqnsR } @Test - void testKeyCacheFromGrants() throws InterruptedException, ExecutionException { + void testKeyCacheFromGrants() { // Create the KasPublicKey object KasPublicKey kasPublicKey1 = KasPublicKey.newBuilder() .setAlg(KasPublicKeyAlgEnum.KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP256R1) From 5da6224b97fc7a6b4b756d60813e7a6f4e26ad76 Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Wed, 23 Apr 2025 09:41:58 -0400 Subject: [PATCH 29/52] cleanup --- .../main/java/io/opentdf/platform/sdk/AddressNormalizer.java | 3 +++ .../test/java/io/opentdf/platform/sdk/AutoconfigureTest.java | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/AddressNormalizer.java b/sdk/src/main/java/io/opentdf/platform/sdk/AddressNormalizer.java index 6ad6d59f..c84bc90c 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/AddressNormalizer.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/AddressNormalizer.java @@ -11,6 +11,9 @@ class AddressNormalizer { private static final Logger logger = LoggerFactory.getLogger(AddressNormalizer.class); + private AddressNormalizer(){ + } + static String normalizeAddress(String urlString, boolean usePlaintext) { URL url; try { diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/AutoconfigureTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/AutoconfigureTest.java index c744d055..74e476b3 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/AutoconfigureTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/AutoconfigureTest.java @@ -586,6 +586,7 @@ public ResponseMessage execute() { @Override public void cancel() { + // not really calling anything } }; }); @@ -907,7 +908,9 @@ void testKeyCacheFromGrants() { public ResponseMessage execute() { return new ResponseMessage.Success<>(getResponseWithGrants(request, List.of(kas1)), Collections.emptyMap(), Collections.emptyMap()); } - @Override public void cancel() {} + @Override public void cancel() { + // not really calling anything + } }; }); From cc2864686743754b6bc466cf626c79c675157e37 Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Wed, 23 Apr 2025 09:49:23 -0400 Subject: [PATCH 30/52] cleanup --- .../test/java/io/opentdf/platform/sdk/KASClientTest.java | 4 ++-- .../test/java/io/opentdf/platform/sdk/SDKBuilderTest.java | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/KASClientTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/KASClientTest.java index 8412b428..45d8fc06 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/KASClientTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/KASClientTest.java @@ -187,7 +187,7 @@ public void rewrap(RewrapRequest request, StreamObserver respons } @Test - public void testAddressNormalizationWithHTTPSClient() { + void testAddressNormalizationWithHTTPSClient() { var lastAddress = new AtomicReference(); var dpopKeypair = CryptoUtils.generateRSAKeypair(); var dpopKey = new RSAKey.Builder((RSAPublicKey) dpopKeypair.getPublic()).privateKey(dpopKeypair.getPrivate()) @@ -205,7 +205,7 @@ public void testAddressNormalizationWithHTTPSClient() { } @Test - public void testAddressNormalizationWithInsecureHTTPClient() { + void testAddressNormalizationWithInsecureHTTPClient() { var lastAddress = new AtomicReference(); var dpopKeypair = CryptoUtils.generateRSAKeypair(); var dpopKey = new RSAKey.Builder((RSAPublicKey) dpopKeypair.getPublic()).privateKey(dpopKeypair.getPrivate()) diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/SDKBuilderTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/SDKBuilderTest.java index 6570f27b..a09275e5 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/SDKBuilderTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/SDKBuilderTest.java @@ -190,7 +190,6 @@ public void getWellKnownConfiguration(GetWellKnownConfigurationRequest request, @Override public void getNamespace(GetNamespaceRequest request, StreamObserver responseObserver) { - var val = Value.newBuilder().setStringValue(issuer).build(); var response = GetNamespaceResponse .newBuilder() .build(); @@ -273,7 +272,7 @@ public ServerCall.Listener interceptCall(ServerCall Date: Mon, 28 Apr 2025 08:04:01 -0400 Subject: [PATCH 31/52] get autocloseable working again --- .../io/opentdf/platform/sdk/KASClient.java | 27 +++--- .../java/io/opentdf/platform/sdk/SDK.java | 46 --------- .../io/opentdf/platform/sdk/SDKBuilder.java | 94 +++++++++++++++---- .../platform/sdk/AddressNormalizerTest.java | 4 +- .../platform/sdk/AutoconfigureTest.java | 1 - .../opentdf/platform/sdk/KASClientTest.java | 32 +++---- 6 files changed, 107 insertions(+), 97 deletions(-) diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java b/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java index b2d01290..81a04e2d 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java @@ -1,6 +1,7 @@ package io.opentdf.platform.sdk; import com.connectrpc.ResponseMessageKt; +import com.connectrpc.impl.ProtocolClient; import com.google.gson.Gson; import com.nimbusds.jose.JOSEException; import com.nimbusds.jose.JWSAlgorithm; @@ -22,7 +23,9 @@ import io.opentdf.platform.sdk.TDF.KasBadRequestException; import kotlin.collections.MapsKt; +import okhttp3.OkHttpClient; +import java.net.http.HttpClient; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.time.Duration; @@ -30,6 +33,8 @@ import java.util.Collections; import java.util.Date; import java.util.HashMap; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; import java.util.function.Function; import static com.connectrpc.ResponseMessageKt.getOrThrow; @@ -43,7 +48,8 @@ */ public class KASClient implements SDK.KAS { - private final Function channelFactory; + private final OkHttpClient httpClient; + private final BiFunction protocolClientFactory; private final boolean usePlaintext; private final RSASSASigner signer; private AsymDecryption decryptor; @@ -53,12 +59,12 @@ public class KASClient implements SDK.KAS { /*** * A client that communicates with KAS * - * @param clientFactory A function that produces channels that can be used to * communicate * @param dpopKey */ - public KASClient(Function clientFactory, RSAKey dpopKey, boolean usePlaintext) { - this.channelFactory = clientFactory; + KASClient(OkHttpClient httpClient, BiFunction protocolClientFactory, RSAKey dpopKey, boolean usePlaintext) { + this.httpClient = httpClient; + this.protocolClientFactory = protocolClientFactory; this.usePlaintext = usePlaintext; try { this.signer = new RSASSASigner(dpopKey); @@ -68,12 +74,6 @@ public KASClient(Function clientFactory, RSAKey dpo this.kasKeyCache = new KASKeyCache(); } - public KASClient(Function channelFactory, boolean usePlaintext, RSASSASigner signer) { - this.channelFactory = channelFactory; - this.usePlaintext = usePlaintext; - this.signer = signer; - } - @Override public KASInfo getECPublicKey(Config.KASInfo kasInfo, NanoTDFType.ECCurve curve) { var req = PublicKeyRequest.newBuilder().setAlgorithm(format("ec:%s", curve.toString())).build(); @@ -126,6 +126,8 @@ public KASKeyCache getKeyCache() { @Override public synchronized void close() { + this.httpClient.dispatcher().cancelAll(); + this.httpClient.connectionPool().evictAll(); } static class RewrapRequestBody { @@ -290,6 +292,9 @@ public byte[] unwrapNanoTDF(NanoTDFType.ECCurve curve, String header, String kas // make this protected so we can test the address normalization logic synchronized AccessServiceClient getStub(String url) { - return stubs.computeIfAbsent(AddressNormalizer.normalizeAddress(url, usePlaintext), channelFactory); + return stubs.computeIfAbsent(AddressNormalizer.normalizeAddress(url, usePlaintext), (String address) -> { + var client = protocolClientFactory.apply(httpClient, address); + return new AccessServiceClient(client); + }); } } diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/SDK.java b/sdk/src/main/java/io/opentdf/platform/sdk/SDK.java index 7e050dc5..4e090bd1 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/SDK.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/SDK.java @@ -1,7 +1,6 @@ package io.opentdf.platform.sdk; import com.connectrpc.Interceptor; -import com.connectrpc.impl.ProtocolClient; import io.opentdf.platform.generated.authorization.AuthorizationServiceClient; import io.opentdf.platform.generated.policy.attributes.AttributesServiceClient; import io.opentdf.platform.generated.policy.namespaces.NamespaceServiceClient; @@ -68,51 +67,6 @@ public interface Services extends AutoCloseable { AuthorizationServiceClient authorization(); KAS kas(); - - static Services newServices(ProtocolClient client, KAS kas) { - var attributeService = new AttributesServiceClient(client); - var namespaceService = new NamespaceServiceClient(client); - var subjectMappingService = new SubjectMappingServiceClient(client); - var resourceMappingService = new ResourceMappingServiceClient(client); - var authorizationService = new AuthorizationServiceClient(client); - - return new Services() { - @Override - public void close() throws Exception { - kas.close(); - } - - @Override - public AttributesServiceClient attributes() { - return attributeService; - } - - @Override - public NamespaceServiceClient namespaces() { - return namespaceService; - } - - @Override - public SubjectMappingServiceClient subjectMappings() { - return subjectMappingService; - } - - @Override - public ResourceMappingServiceClient resourceMappings() { - return resourceMappingService; - } - - @Override - public AuthorizationServiceClient authorization() { - return authorizationService; - } - - @Override - public KAS kas() { - return kas; - } - }; - } } public Optional getTrustManager() { diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java b/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java index 68852a2d..8d5cee0d 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java @@ -26,7 +26,11 @@ import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata; import io.grpc.Status; import io.grpc.StatusRuntimeException; -import io.opentdf.platform.generated.kas.AccessServiceClient; +import io.opentdf.platform.generated.authorization.AuthorizationServiceClient; +import io.opentdf.platform.generated.policy.attributes.AttributesServiceClient; +import io.opentdf.platform.generated.policy.namespaces.NamespaceServiceClient; +import io.opentdf.platform.generated.policy.resourcemapping.ResourceMappingServiceClient; +import io.opentdf.platform.generated.policy.subjectmapping.SubjectMappingServiceClient; import io.opentdf.platform.generated.wellknownconfiguration.GetWellKnownConfigurationRequest; import io.opentdf.platform.generated.wellknownconfiguration.GetWellKnownConfigurationResponse; import io.opentdf.platform.generated.wellknownconfiguration.WellKnownServiceClient; @@ -49,6 +53,7 @@ import java.util.Collections; import java.util.List; import java.util.UUID; +import java.util.function.BiFunction; /** * A builder class for creating instances of the SDK class. @@ -171,7 +176,8 @@ private Interceptor getAuthInterceptor(RSAKey rsaKey) { // well known endpoint ProtocolClient bootstrapClient = null; GetWellKnownConfigurationResponse config; - bootstrapClient = getUnauthenticatedProtocolClient(platformEndpoint) ; + var httpClient = getHttpClient(); + bootstrapClient = getUnauthenticatedProtocolClient(platformEndpoint, httpClient) ; var stub = new WellKnownServiceClient(bootstrapClient); try { config = ResponseMessageKt.getOrThrow(stub.getWellKnownConfigurationBlocking(GetWellKnownConfigurationRequest.getDefaultInstance(), Collections.emptyMap()).execute()); @@ -239,17 +245,63 @@ ServicesAndInternals buildServices() { this.platformEndpoint = AddressNormalizer.normalizeAddress(this.platformEndpoint, this.usePlainText); var authInterceptor = getAuthInterceptor(dpopKey); var kasClient = getKASClient(dpopKey, authInterceptor); - var protocolClient = getProtocolClient(platformEndpoint, authInterceptor); + var httpClient = getHttpClient(); + var client = getProtocolClient(platformEndpoint, httpClient, authInterceptor); + var attributeService = new AttributesServiceClient(client); + var namespaceService = new NamespaceServiceClient(client); + var subjectMappingService = new SubjectMappingServiceClient(client); + var resourceMappingService = new ResourceMappingServiceClient(client); + var authorizationService = new AuthorizationServiceClient(client); + + var services = new SDK.Services() { + @Override + public void close() { + kasClient.close(); + httpClient.dispatcher().executorService().shutdown(); + httpClient.connectionPool().evictAll(); + } + + @Override + public AttributesServiceClient attributes() { + return attributeService; + } + + @Override + public NamespaceServiceClient namespaces() { + return namespaceService; + } + + @Override + public SubjectMappingServiceClient subjectMappings() { + return subjectMappingService; + } + + @Override + public ResourceMappingServiceClient resourceMappings() { + return resourceMappingService; + } + + @Override + public AuthorizationServiceClient authorization() { + return authorizationService; + } + + @Override + public SDK.KAS kas() { + return kasClient; + } + }; return new ServicesAndInternals( authInterceptor, sslFactory == null ? null : sslFactory.getTrustManager().orElse(null), - SDK.Services.newServices(protocolClient, kasClient)); + services); } @Nonnull private KASClient getKASClient(RSAKey dpopKey, Interceptor interceptor) { - return new KASClient((String endpoint) -> new AccessServiceClient(getProtocolClient(endpoint, interceptor)), dpopKey, usePlainText); + BiFunction protocolClientFactory = (OkHttpClient client, String address) -> getProtocolClient(address, client, interceptor); + return new KASClient(getHttpClient(), protocolClientFactory, dpopKey, usePlainText); } public SDK build() { @@ -257,11 +309,25 @@ public SDK build() { return new SDK(services.services, services.trustManager, services.interceptor); } - private ProtocolClient getUnauthenticatedProtocolClient(String endpoint) { - return getProtocolClient(endpoint, null); + private ProtocolClient getUnauthenticatedProtocolClient(String endpoint, OkHttpClient httpClient) { + return getProtocolClient(endpoint, httpClient, null); + } + + private ProtocolClient getProtocolClient(String endpoint, OkHttpClient httpClient, Interceptor authInterceptor) { + var protocolClientConfig = new ProtocolClientConfig( + endpoint, + new GoogleJavaProtobufStrategy(), + NetworkProtocol.GRPC, + null, + GETConfiguration.Enabled.INSTANCE, + authInterceptor == null ? Collections.emptyList() : List.of(ignoredConfig -> authInterceptor) + ); + + return new ProtocolClient(new ConnectOkHttpClient(httpClient), protocolClientConfig); } - private ProtocolClient getProtocolClient(String endpoint, Interceptor authInterceptor) { + private OkHttpClient getHttpClient() { + // TODO: just use a single http client for everything var httpClient = new OkHttpClient.Builder(); if (usePlainText) { // we can only connect using HTTP/2 without any negotiation when using plain test @@ -273,17 +339,7 @@ private ProtocolClient getProtocolClient(String endpoint, Interceptor authInterc } httpClient.sslSocketFactory(sslFactory.getSslSocketFactory(), sslFactory.getTrustManager().get()); } - - var protocolClientConfig = new ProtocolClientConfig( - endpoint, - new GoogleJavaProtobufStrategy(), - NetworkProtocol.GRPC, - null, - GETConfiguration.Enabled.INSTANCE, - authInterceptor == null ? Collections.emptyList() : List.of(ignoredConfig -> authInterceptor) - ); - - return new ProtocolClient(new ConnectOkHttpClient(httpClient.build()), protocolClientConfig); + return httpClient.build(); } SSLFactory getSslFactory() { diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/AddressNormalizerTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/AddressNormalizerTest.java index 0786a7ac..65794374 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/AddressNormalizerTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/AddressNormalizerTest.java @@ -9,14 +9,14 @@ class AddressNormalizerTest { @Test - public void testAddressNormalizationWithHTTPSClient() { + void testAddressNormalizationWithHTTPSClient() { assertThat(normalizeAddress("http://example.org", false)).isEqualTo("https://example.org:80"); // default to https if no scheme is provided assertThat(normalizeAddress("example.org:1234", false)).isEqualTo("https://example.org:1234"); } @Test - public void testAddressNormaliationWithInsecureHTTPClient() { + void testAddressNormaliationWithInsecureHTTPClient() { assertThat(normalizeAddress("http://localhost:8080", true)).isEqualTo("http://localhost:8080"); assertThat(normalizeAddress("https://example.org", true)).isEqualTo("https://example.org:443"); // default to http if no scheme is provided diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/AutoconfigureTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/AutoconfigureTest.java index 74e476b3..6f91f6e7 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/AutoconfigureTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/AutoconfigureTest.java @@ -40,7 +40,6 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; import java.util.regex.Matcher; import java.util.regex.Pattern; diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/KASClientTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/KASClientTest.java index 45d8fc06..62ad5921 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/KASClientTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/KASClientTest.java @@ -16,7 +16,6 @@ import io.grpc.Server; import io.grpc.ServerBuilder; import io.grpc.stub.StreamObserver; -import io.opentdf.platform.generated.kas.AccessServiceClient; import io.opentdf.platform.generated.kas.AccessServiceGrpc; import io.opentdf.platform.generated.kas.PublicKeyRequest; import io.opentdf.platform.generated.kas.PublicKeyResponse; @@ -33,24 +32,21 @@ import java.util.List; import java.util.Random; import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Function; +import java.util.function.BiFunction; import static io.opentdf.platform.sdk.SDKBuilderTest.getRandomPort; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; public class KASClientTest { + OkHttpClient httpClient = new OkHttpClient.Builder() + .protocols(List.of(Protocol.H2_PRIOR_KNOWLEDGE)) + .build(); - Function aclientFactory = (String endpoint) -> { - var c = new OkHttpClient.Builder() - .protocols(List.of(Protocol.H2_PRIOR_KNOWLEDGE)) - .build(); - - var as = new ProtocolClient( - new ConnectOkHttpClient(c), + BiFunction aclientFactory = (OkHttpClient client, String endpoint) -> { + return new ProtocolClient( + new ConnectOkHttpClient(httpClient), new ProtocolClientConfig(endpoint, new GoogleJavaProtobufStrategy(), NetworkProtocol.GRPC, null, GETConfiguration.Enabled.INSTANCE) ); - - return new AccessServiceClient(as); }; @Test @@ -72,7 +68,7 @@ public void publicKey(PublicKeyRequest request, StreamObserver respons rewrapServer = startServer(accessService); byte[] plaintextKey; byte[] rewrapResponse; - try (var kas = new KASClient(aclientFactory, dpopKey, true)) { + try (var kas = new KASClient(httpClient, aclientFactory, dpopKey, true)) { Manifest.KeyAccess keyAccess = new Manifest.KeyAccess(); keyAccess.url = "http://localhost:" + rewrapServer.getPort(); @@ -192,9 +188,9 @@ void testAddressNormalizationWithHTTPSClient() { var dpopKeypair = CryptoUtils.generateRSAKeypair(); var dpopKey = new RSAKey.Builder((RSAPublicKey) dpopKeypair.getPublic()).privateKey(dpopKeypair.getPrivate()) .build(); - var httpsKASClient = new KASClient(addr -> { + var httpsKASClient = new KASClient(httpClient, (client, addr) -> { lastAddress.set(addr); - return aclientFactory.apply(addr); + return aclientFactory.apply(client, addr); }, dpopKey, false); var stub = httpsKASClient.getStub("http://localhost:8080"); @@ -210,9 +206,9 @@ void testAddressNormalizationWithInsecureHTTPClient() { var dpopKeypair = CryptoUtils.generateRSAKeypair(); var dpopKey = new RSAKey.Builder((RSAPublicKey) dpopKeypair.getPublic()).privateKey(dpopKeypair.getPrivate()) .build(); - var httpsKASClient = new KASClient(addr -> { + var httpsKASClient = new KASClient(httpClient, (client, addr) -> { lastAddress.set(addr); - return aclientFactory.apply(addr); + return aclientFactory.apply(client, addr); }, dpopKey, true); var c1 = httpsKASClient.getStub("http://example.org"); From 527f7106466702d23961ea6788e7199ac1084738 Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Mon, 28 Apr 2025 08:41:22 -0400 Subject: [PATCH 32/52] sonar --- sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java | 3 --- sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java | 3 ++- sdk/src/main/java/io/opentdf/platform/sdk/TokenSource.java | 5 ++--- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java b/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java index 81a04e2d..11c3b5d2 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java @@ -25,7 +25,6 @@ import kotlin.collections.MapsKt; import okhttp3.OkHttpClient; -import java.net.http.HttpClient; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.time.Duration; @@ -33,9 +32,7 @@ import java.util.Collections; import java.util.Date; import java.util.HashMap; -import java.util.function.BiConsumer; import java.util.function.BiFunction; -import java.util.function.Function; import static com.connectrpc.ResponseMessageKt.getOrThrow; import static io.opentdf.platform.sdk.TDF.GLOBAL_KEY_SALT; diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java b/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java index 8d5cee0d..7cf9a6dc 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java @@ -327,7 +327,8 @@ private ProtocolClient getProtocolClient(String endpoint, OkHttpClient httpClien } private OkHttpClient getHttpClient() { - // TODO: just use a single http client for everything + // using a single http client is apparently the best practice, subject to everyone wanting to + // have the same protocols var httpClient = new OkHttpClient.Builder(); if (usePlainText) { // we can only connect using HTTP/2 without any negotiation when using plain test diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/TokenSource.java b/sdk/src/main/java/io/opentdf/platform/sdk/TokenSource.java index 8a4c2f71..97e02a0c 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/TokenSource.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/TokenSource.java @@ -124,7 +124,7 @@ private synchronized AccessToken getToken() { tokenResponse = TokenResponse.parse(httpResponse); if (!tokenResponse.indicatesSuccess()) { ErrorObject error = tokenResponse.toErrorResponse().getErrorObject(); - throw new RuntimeException("Token request failed: " + error); + throw new SDKException("failure to get token. description = [" + error.getDescription() + "] error code = [" + error.getCode() + "] error uri = [" + error.getURI() + "]"); } var tokens = tokenResponse.toSuccessResponse().getTokens(); @@ -149,8 +149,7 @@ private synchronized AccessToken getToken() { } } catch (Exception e) { - // TODO Auto-generated catch block - throw new RuntimeException("failed to get token", e); + throw new SDKException("failed to get token", e); } return this.token; } From fa26a9c155e99a9bc3d8732a57a29d34a1c27893 Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Mon, 28 Apr 2025 09:19:11 -0400 Subject: [PATCH 33/52] sonar cloud --- sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java b/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java index 7cf9a6dc..597b61a8 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java @@ -335,10 +335,11 @@ private OkHttpClient getHttpClient() { httpClient.protocols(List.of(Protocol.H2_PRIOR_KNOWLEDGE)); } if (sslFactory != null) { - if (sslFactory.getTrustManager().isEmpty()) { + var trustManager = sslFactory.getTrustManager(); + if (trustManager.isEmpty()) { throw new SDKException("SSL factory must have a trust manager"); } - httpClient.sslSocketFactory(sslFactory.getSslSocketFactory(), sslFactory.getTrustManager().get()); + httpClient.sslSocketFactory(sslFactory.getSslSocketFactory(), trustManager.get()); } return httpClient.build(); } From 60f9a04d932e2a068f1225f0fbe2a0d7538c6d59 Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Mon, 28 Apr 2025 09:31:10 -0400 Subject: [PATCH 34/52] Delete keys/kas-cert.pem --- keys/kas-cert.pem | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 keys/kas-cert.pem diff --git a/keys/kas-cert.pem b/keys/kas-cert.pem deleted file mode 100644 index 2e8f6bb0..00000000 --- a/keys/kas-cert.pem +++ /dev/null @@ -1,19 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIC/TCCAeWgAwIBAgIUUCu5Zu5b8PIRhAFJ5VpaZFP35HQwDQYJKoZIhvcNAQEL -BQAwDjEMMAoGA1UEAwwDa2FzMB4XDTI0MDgxNTExMzYxN1oXDTI1MDgxNTExMzYx -N1owDjEMMAoGA1UEAwwDa2FzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC -AQEAx4PgRz+H/U4X5hf3Uj2JgqqyG7fX0QDSoxI3Ziu1Q+gbxElVppOqJQMA88Yd -8uug2Px/vtmkFwGXGi5DzKq6bLcpCJEXc1TOtJH5o+2Wcu1Ahfp9MxkOXpbJvoH8 -JRkjOp6ZPIiIM/IOQDq3eHpLVAB6ihDFDwzhJsqBMVMejmkDRNj8qx5AkZSrE4zi -AL7hV/TWWyCiq8rLiWVnZOFXNHyRtPmTgmerRg5Ad1lP9muMrLJ/1ziq1lILk7fB -a31yOmS3g25MGYYwX+7PCNMWkhX0eCLAyosYfIp/K0SOJ3WO9G4eiq9keb4xRSbB -jFKmadNBITEWIhPzCAzT8nDlFwIDAQABo1MwUTAdBgNVHQ4EFgQUFc94TI8PUU+u -uASVIyQgQm4tHRswHwYDVR0jBBgwFoAUFc94TI8PUU+uuASVIyQgQm4tHRswDwYD -VR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAjvzUeU9otYOM0UpwhZ/j -nBxNcP0J7fAODokL1as8zXnv7t8mk327b8HSORkfGdbCsf5be10W9xGK1DoRhscA -3wkASiWOk2wsIq9l12tR247EJXk7VMcDXQfGuVhzMhN1gp25bxX2FsmKvxnOFuN2 -Qv4BY61dQSHjoDQDRhYb9naYTqsppiHjj11nayfEY6nVivs9Hu9jXqakcE4wSksX -DRRgxAs2KBcbQ0/rfOZs7yPs8jlqpmPk09M+yV7Tn1943EaAnWyiuavW7g5Zn/dM -szuJrlIzmgRdUyHTD4tS6ebTqGo+hziYNdfHUdjQF8JDMiRFwx/xoqC5/1jP18kX -MQ== ------END CERTIFICATE----- From c64d166f889353206aa73d8d92cca133b89c4aef Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Mon, 28 Apr 2025 13:34:46 -0400 Subject: [PATCH 35/52] sonar --- sdk/src/main/java/io/opentdf/platform/sdk/NanoTDF.java | 4 ++-- sdk/src/test/java/io/opentdf/platform/sdk/NanoTDFTest.java | 4 +--- sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java | 6 +++--- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/NanoTDF.java b/sdk/src/main/java/io/opentdf/platform/sdk/NanoTDF.java index bdecb330..09848387 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/NanoTDF.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/NanoTDF.java @@ -212,12 +212,12 @@ public void readNanoTDF(ByteBuffer nanoTDF, OutputStream outputStream, } public void readNanoTDF(ByteBuffer nanoTDF, OutputStream outputStream, - SDK.KAS kas, KeyAccessServerRegistryServiceClient kasRegistryService, String platformUrl) throws IOException, InterruptedException, ExecutionException, URISyntaxException { + SDK.KAS kas, KeyAccessServerRegistryServiceClient kasRegistryService, String platformUrl) throws IOException, URISyntaxException { readNanoTDF(nanoTDF, outputStream,kas, Config.newNanoTDFReaderConfig(), kasRegistryService, platformUrl); } public void readNanoTDF(ByteBuffer nanoTDF, OutputStream outputStream, - SDK.KAS kas, Config.NanoTDFReaderConfig nanoTdfReaderConfig, KeyAccessServerRegistryServiceClient kasRegistryService, String platformUrl) throws IOException, InterruptedException, ExecutionException, URISyntaxException { + SDK.KAS kas, Config.NanoTDFReaderConfig nanoTdfReaderConfig, KeyAccessServerRegistryServiceClient kasRegistryService, String platformUrl) throws IOException, URISyntaxException { if (!nanoTdfReaderConfig.ignoreKasAllowlist && (nanoTdfReaderConfig.kasAllowlist == null || nanoTdfReaderConfig.kasAllowlist.isEmpty())) { ListKeyAccessServersRequest request = ListKeyAccessServersRequest.newBuilder() .build(); diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/NanoTDFTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/NanoTDFTest.java index c0a0c017..91835cd3 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/NanoTDFTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/NanoTDFTest.java @@ -145,11 +145,9 @@ public ResponseMessage execute() { @Override public void cancel() { + // this never happens in tests } }); -// io.grpc.Channel mockChannel = mock(io.grpc.Channel.class); -// when(mockChannel.authority()).thenReturn("mock:8080"); -// when(kasRegistryService.getChannel()).thenReturn(mockChannel); } private static ArrayList keypairs = new ArrayList<>(); diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java index cb8d6a45..a145ed6e 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java @@ -159,7 +159,7 @@ public ResponseMessage execute() { @Override public void cancel() { - + // this never happens in tests } }); } @@ -555,7 +555,7 @@ public void testCreateTDFWithMimeType() throws Exception { } @Test - public void legacyTDFRoundTrips() throws DecoderException, IOException, ExecutionException, JOSEException, InterruptedException, ParseException, NoSuchAlgorithmException, URISyntaxException { + public void legacyTDFRoundTrips() throws DecoderException, IOException, JOSEException, ParseException, NoSuchAlgorithmException, URISyntaxException { final String mimeType = "application/pdf"; var assertionConfig1 = new AssertionConfig(); assertionConfig1.id = "assertion1"; @@ -637,7 +637,7 @@ public ResponseMessage execute() { @Override public void cancel() { - + // we never do this during tests } } ); From 33e89256dcbab18c804c8caf2fd79cf1b830515a Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Mon, 28 Apr 2025 13:40:26 -0400 Subject: [PATCH 36/52] more sonar --- sdk/src/main/java/io/opentdf/platform/sdk/NanoTDF.java | 1 - sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/NanoTDF.java b/sdk/src/main/java/io/opentdf/platform/sdk/NanoTDF.java index 09848387..c9554140 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/NanoTDF.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/NanoTDF.java @@ -15,7 +15,6 @@ import java.nio.charset.StandardCharsets; import java.security.*; import java.util.*; -import java.util.concurrent.ExecutionException; import com.google.gson.Gson; import com.google.gson.GsonBuilder; diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java index a145ed6e..04a0b43b 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java @@ -24,7 +24,6 @@ import java.io.OutputStream; import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; -import java.security.Key; import java.security.KeyPair; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; @@ -34,7 +33,6 @@ import java.util.Collections; import java.util.List; import java.util.Random; -import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Predicate; import java.util.regex.Pattern; @@ -555,7 +553,7 @@ public void testCreateTDFWithMimeType() throws Exception { } @Test - public void legacyTDFRoundTrips() throws DecoderException, IOException, JOSEException, ParseException, NoSuchAlgorithmException, URISyntaxException { + void legacyTDFRoundTrips() throws DecoderException, IOException, JOSEException, ParseException, NoSuchAlgorithmException, URISyntaxException { final String mimeType = "application/pdf"; var assertionConfig1 = new AssertionConfig(); assertionConfig1.id = "assertion1"; From bb69d45a58d92fba205226389b18b79b6b08d2a7 Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Wed, 30 Apr 2025 07:27:25 -0400 Subject: [PATCH 37/52] log some more info --- sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java | 4 ++++ sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java | 3 +-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java b/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java index 11c3b5d2..fb391163 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java @@ -24,6 +24,8 @@ import kotlin.collections.MapsKt; import okhttp3.OkHttpClient; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -45,6 +47,7 @@ */ public class KASClient implements SDK.KAS { + private static final Logger log = LoggerFactory.getLogger(KASClient.class); private final OkHttpClient httpClient; private final BiFunction protocolClientFactory; private final boolean usePlaintext; @@ -262,6 +265,7 @@ public byte[] unwrapNanoTDF(NanoTDFType.ECCurve curve, String header, String kas try { response = getOrThrow(request); } catch (Exception e) { + log.error("this is the error", e.getCause()); throw new SDKException("error rewrapping key", e); } var wrappedKey = response.getEntityWrappedKey().toByteArray(); diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java b/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java index 51ed9dab..397a558f 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java @@ -173,8 +173,7 @@ private Interceptor getAuthInterceptor(RSAKey rsaKey) { } // we don't add the auth listener to this channel since it is only used to call - // the - // well known endpoint + // the well known endpoint ProtocolClient bootstrapClient = null; GetWellKnownConfigurationResponse config; var httpClient = getHttpClient(); From e679d36cb7ae994b2d3c5265a1db7d13d7990869 Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Wed, 30 Apr 2025 07:48:52 -0400 Subject: [PATCH 38/52] also do it here --- sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java | 1 + 1 file changed, 1 insertion(+) diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java b/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java index fb391163..44d4b20b 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java @@ -203,6 +203,7 @@ public byte[] unwrap(Manifest.KeyAccess keyAccess, String policy, KeyType sessi } throw e; } catch (Exception e) { + log.error("error unwrapping key", e); throw new SDKException("error unwrapping key", e); } var wrappedKey = response.getEntityWrappedKey().toByteArray(); From 3675291361fb6388de88d268455d9b3675294229 Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Wed, 30 Apr 2025 09:42:41 -0400 Subject: [PATCH 39/52] fix error handling --- .../io/opentdf/platform/sdk/KASClient.java | 13 ++++++------- .../io/opentdf/platform/sdk/SDKBuilder.java | 11 ++++------- .../io/opentdf/platform/sdk/RequestHelper.kt | 19 +++++++++++++++++++ 3 files changed, 29 insertions(+), 14 deletions(-) create mode 100644 sdk/src/main/kotlin/io/opentdf/platform/sdk/RequestHelper.kt diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java b/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java index 44d4b20b..4bb854ad 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java @@ -1,5 +1,7 @@ package io.opentdf.platform.sdk; +import com.connectrpc.Code; +import com.connectrpc.ConnectException; import com.connectrpc.ResponseMessageKt; import com.connectrpc.impl.ProtocolClient; import com.google.gson.Gson; @@ -10,8 +12,6 @@ import com.nimbusds.jose.jwk.RSAKey; import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.SignedJWT; -import io.grpc.StatusRuntimeException; -import io.grpc.Status; import io.opentdf.platform.generated.kas.AccessServiceClient; import io.opentdf.platform.generated.kas.PublicKeyRequest; import io.opentdf.platform.generated.kas.PublicKeyResponse; @@ -195,15 +195,14 @@ public byte[] unwrap(Manifest.KeyAccess keyAccess, String policy, KeyType sessi RewrapResponse response; var req = getStub(keyAccess.url).rewrapBlocking(request, Collections.emptyMap()).execute(); try { - response = getOrThrow(req); - } catch (StatusRuntimeException e) { - if (e.getStatus().getCode() == Status.Code.INVALID_ARGUMENT) { + response = RequestHelper.getOrThrow(req); + } catch (ConnectException e) { + if (e.getCode() == Code.INVALID_ARGUMENT) { // 400 Bad Request throw new KasBadRequestException("rewrap request 400: " + e); } - throw e; + throw new SDKException("error unwrapping key", e); } catch (Exception e) { - log.error("error unwrapping key", e); throw new SDKException("error unwrapping key", e); } var wrappedKey = response.getEntityWrappedKey().toByteArray(); diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java b/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java index 397a558f..da898927 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java @@ -1,8 +1,8 @@ package io.opentdf.platform.sdk; +import com.connectrpc.ConnectException; import com.connectrpc.Interceptor; import com.connectrpc.ProtocolClientConfig; -import com.connectrpc.ResponseMessageKt; import com.connectrpc.extensions.GoogleJavaProtobufStrategy; import com.connectrpc.impl.ProtocolClient; import com.connectrpc.okhttp.ConnectOkHttpClient; @@ -24,8 +24,6 @@ import com.nimbusds.oauth2.sdk.token.TokenTypeURI; import com.nimbusds.oauth2.sdk.tokenexchange.TokenExchangeGrant; import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata; -import io.grpc.Status; -import io.grpc.StatusRuntimeException; import io.opentdf.platform.generated.authorization.AuthorizationServiceClient; import io.opentdf.platform.generated.policy.attributes.AttributesServiceClient; import io.opentdf.platform.generated.policy.kasregistry.KeyAccessServerRegistryServiceClient; @@ -180,10 +178,9 @@ private Interceptor getAuthInterceptor(RSAKey rsaKey) { bootstrapClient = getUnauthenticatedProtocolClient(platformEndpoint, httpClient) ; var stub = new WellKnownServiceClient(bootstrapClient); try { - config = ResponseMessageKt.getOrThrow(stub.getWellKnownConfigurationBlocking(GetWellKnownConfigurationRequest.getDefaultInstance(), Collections.emptyMap()).execute()); - } catch (StatusRuntimeException e) { - Status status = Status.fromThrowable(e); - throw new SDKException(String.format("Got grpc status [%s] when getting configuration", status), e); + config = RequestHelper.getOrThrow(stub.getWellKnownConfigurationBlocking(GetWellKnownConfigurationRequest.getDefaultInstance(), Collections.emptyMap()).execute()); + } catch (ConnectException e) { + throw new SDKException(String.format("Got grpc status [%s] when getting configuration", e.getCode()), e); } String platformIssuer; diff --git a/sdk/src/main/kotlin/io/opentdf/platform/sdk/RequestHelper.kt b/sdk/src/main/kotlin/io/opentdf/platform/sdk/RequestHelper.kt new file mode 100644 index 00000000..841d4cc5 --- /dev/null +++ b/sdk/src/main/kotlin/io/opentdf/platform/sdk/RequestHelper.kt @@ -0,0 +1,19 @@ +package io.opentdf.platform.sdk + +import com.connectrpc.ConnectException +import com.connectrpc.ResponseMessage +import com.connectrpc.getOrThrow + +class RequestHelper { + companion object { + /** + * Kotlin doesn't have checked exceptions so that if we want to catch + * an exception thrown by Kotlin we need to declare that the method throws it. + * ResponseMessageKt.getOrThrow() doesn't do so so we need to wrap it + */ + @JvmStatic @Throws(ConnectException::class) + fun getOrThrow(responseMessage: ResponseMessage): T { + return responseMessage.getOrThrow(); + } + } +} \ No newline at end of file From 6ebae76eba8d22b39ba472ed71f57cc9d07725df Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Wed, 30 Apr 2025 09:52:22 -0400 Subject: [PATCH 40/52] clarity --- sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java | 6 ++---- .../main/kotlin/io/opentdf/platform/sdk/RequestHelper.kt | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java b/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java index 4bb854ad..e73ce4c2 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java @@ -36,7 +36,6 @@ import java.util.HashMap; import java.util.function.BiFunction; -import static com.connectrpc.ResponseMessageKt.getOrThrow; import static io.opentdf.platform.sdk.TDF.GLOBAL_KEY_SALT; import static java.lang.String.format; @@ -104,7 +103,7 @@ public Config.KASInfo getPublicKey(Config.KASInfo kasInfo) { var req= getStub(kasInfo.URL).publicKeyBlocking(request, MapsKt.mapOf()).execute(); PublicKeyResponse resp; try { - resp = getOrThrow(req); + resp = RequestHelper.getOrThrow(req); } catch (Exception e) { throw new SDKException("error getting public key", e); } @@ -263,9 +262,8 @@ public byte[] unwrapNanoTDF(NanoTDFType.ECCurve curve, String header, String kas var request = getStub(keyAccess.url).rewrapBlocking(req, Collections.emptyMap()).execute(); RewrapResponse response; try { - response = getOrThrow(request); + response = RequestHelper.getOrThrow(request); } catch (Exception e) { - log.error("this is the error", e.getCause()); throw new SDKException("error rewrapping key", e); } var wrappedKey = response.getEntityWrappedKey().toByteArray(); diff --git a/sdk/src/main/kotlin/io/opentdf/platform/sdk/RequestHelper.kt b/sdk/src/main/kotlin/io/opentdf/platform/sdk/RequestHelper.kt index 841d4cc5..a8ce4b7f 100644 --- a/sdk/src/main/kotlin/io/opentdf/platform/sdk/RequestHelper.kt +++ b/sdk/src/main/kotlin/io/opentdf/platform/sdk/RequestHelper.kt @@ -9,7 +9,7 @@ class RequestHelper { /** * Kotlin doesn't have checked exceptions so that if we want to catch * an exception thrown by Kotlin we need to declare that the method throws it. - * ResponseMessageKt.getOrThrow() doesn't do so so we need to wrap it + * ResponseMessageKt.getOrThrow() doesn't do so; so we need to wrap it */ @JvmStatic @Throws(ConnectException::class) fun getOrThrow(responseMessage: ResponseMessage): T { From d7940c47981d8b641ee63e6cadf9fd083a669841 Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Wed, 30 Apr 2025 10:01:20 -0400 Subject: [PATCH 41/52] better comment --- .../main/kotlin/io/opentdf/platform/sdk/RequestHelper.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/sdk/src/main/kotlin/io/opentdf/platform/sdk/RequestHelper.kt b/sdk/src/main/kotlin/io/opentdf/platform/sdk/RequestHelper.kt index a8ce4b7f..957c3f0b 100644 --- a/sdk/src/main/kotlin/io/opentdf/platform/sdk/RequestHelper.kt +++ b/sdk/src/main/kotlin/io/opentdf/platform/sdk/RequestHelper.kt @@ -7,9 +7,11 @@ import com.connectrpc.getOrThrow class RequestHelper { companion object { /** - * Kotlin doesn't have checked exceptions so that if we want to catch - * an exception thrown by Kotlin we need to declare that the method throws it. - * ResponseMessageKt.getOrThrow() doesn't do so; so we need to wrap it + * Kotlin doesn't have checked exceptions (importantly it doesn't declare them). + * This means that if a Kotlin function throws a checked exception, you can't + * catch it in Java unless it uses the {@class kotlin.jvm.Throws} annotation. + * We wrap the getOrThrow() method in a static method with the annotation we + * need to catch a {@class ConnectException} in Java. */ @JvmStatic @Throws(ConnectException::class) fun getOrThrow(responseMessage: ResponseMessage): T { From 1200d46db06ab2a8b3e128af0d30824f2b083e94 Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Wed, 30 Apr 2025 10:08:09 -0400 Subject: [PATCH 42/52] sonar --- sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java | 1 - 1 file changed, 1 deletion(-) diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java b/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java index e73ce4c2..2d9ceb85 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java @@ -46,7 +46,6 @@ */ public class KASClient implements SDK.KAS { - private static final Logger log = LoggerFactory.getLogger(KASClient.class); private final OkHttpClient httpClient; private final BiFunction protocolClientFactory; private final boolean usePlaintext; From 347a995c990e7905cf69dc30e05b8cc5087e6bb3 Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Wed, 30 Apr 2025 10:12:20 -0400 Subject: [PATCH 43/52] even more sonar --- sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java b/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java index 2d9ceb85..2e09b461 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java @@ -22,10 +22,7 @@ import io.opentdf.platform.sdk.nanotdf.NanoTDFType; import io.opentdf.platform.sdk.TDF.KasBadRequestException; -import kotlin.collections.MapsKt; import okhttp3.OkHttpClient; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -75,7 +72,7 @@ public class KASClient implements SDK.KAS { @Override public KASInfo getECPublicKey(Config.KASInfo kasInfo, NanoTDFType.ECCurve curve) { var req = PublicKeyRequest.newBuilder().setAlgorithm(format("ec:%s", curve.toString())).build(); - var r = getStub(kasInfo.URL).publicKeyBlocking(req, MapsKt.mapOf()).execute(); + var r = getStub(kasInfo.URL).publicKeyBlocking(req, Collections.emptyMap()).execute(); PublicKeyResponse res; try { res = ResponseMessageKt.getOrThrow(r); @@ -99,7 +96,7 @@ public Config.KASInfo getPublicKey(Config.KASInfo kasInfo) { ? PublicKeyRequest.getDefaultInstance() : PublicKeyRequest.newBuilder().setAlgorithm(kasInfo.Algorithm).build(); - var req= getStub(kasInfo.URL).publicKeyBlocking(request, MapsKt.mapOf()).execute(); + var req = getStub(kasInfo.URL).publicKeyBlocking(request, Collections.emptyMap()).execute(); PublicKeyResponse resp; try { resp = RequestHelper.getOrThrow(req); From 15f78529d97c6285557794cb3f8a29708442d595 Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Wed, 7 May 2025 14:12:06 -0400 Subject: [PATCH 44/52] `io.opentdf.sdk.generated` -> `io.opentdf.sdk` for connect-rpc classes --- .../io/opentdf/platform/CreateAttribute.java | 8 +++---- .../io/opentdf/platform/CreateNamespace.java | 4 ++-- .../platform/CreateSubjectConditionSet.java | 18 +++++++------- .../platform/CreateSubjectMapping.java | 8 +++---- .../io/opentdf/platform/GetDecisions.java | 16 ++++++------- .../io/opentdf/platform/GetEntitlements.java | 8 +++---- .../io/opentdf/platform/ListAttributes.java | 6 ++--- .../io/opentdf/platform/ListNamespaces.java | 6 ++--- .../opentdf/platform/ListSubjectMappings.java | 6 ++--- sdk/buf.gen.yaml | 4 ++-- .../opentdf/platform/sdk/Autoconfigure.java | 20 ++++++++-------- .../java/io/opentdf/platform/sdk/Config.java | 2 +- .../io/opentdf/platform/sdk/KASClient.java | 10 ++++---- .../java/io/opentdf/platform/sdk/NanoTDF.java | 6 ++--- .../java/io/opentdf/platform/sdk/SDK.java | 12 +++++----- .../io/opentdf/platform/sdk/SDKBuilder.java | 18 +++++++------- .../java/io/opentdf/platform/sdk/TDF.java | 10 ++++---- .../platform/sdk/AutoconfigureTest.java | 24 +++++++++---------- .../opentdf/platform/sdk/KASClientTest.java | 10 ++++---- .../io/opentdf/platform/sdk/NanoTDFTest.java | 8 +++---- .../opentdf/platform/sdk/SDKBuilderTest.java | 18 +++++++------- .../java/io/opentdf/platform/sdk/TDFTest.java | 8 +++---- 22 files changed, 115 insertions(+), 115 deletions(-) diff --git a/examples/src/main/java/io/opentdf/platform/CreateAttribute.java b/examples/src/main/java/io/opentdf/platform/CreateAttribute.java index b5fdba33..480b4ecb 100644 --- a/examples/src/main/java/io/opentdf/platform/CreateAttribute.java +++ b/examples/src/main/java/io/opentdf/platform/CreateAttribute.java @@ -1,9 +1,9 @@ package io.opentdf.platform; import com.connectrpc.ResponseMessageKt; -import io.opentdf.platform.generated.policy.Attribute; -import io.opentdf.platform.generated.policy.AttributeRuleTypeEnum; -import io.opentdf.platform.generated.policy.attributes.CreateAttributeRequest; -import io.opentdf.platform.generated.policy.attributes.CreateAttributeResponse; +import io.opentdf.platform.policy.Attribute; +import io.opentdf.platform.policy.AttributeRuleTypeEnum; +import io.opentdf.platform.policy.attributes.CreateAttributeRequest; +import io.opentdf.platform.policy.attributes.CreateAttributeResponse; import io.opentdf.platform.sdk.*; import java.util.Collections; diff --git a/examples/src/main/java/io/opentdf/platform/CreateNamespace.java b/examples/src/main/java/io/opentdf/platform/CreateNamespace.java index 0a85a7f7..736d8dce 100644 --- a/examples/src/main/java/io/opentdf/platform/CreateNamespace.java +++ b/examples/src/main/java/io/opentdf/platform/CreateNamespace.java @@ -1,7 +1,7 @@ package io.opentdf.platform; import com.connectrpc.ResponseMessageKt; -import io.opentdf.platform.generated.policy.namespaces.CreateNamespaceRequest; -import io.opentdf.platform.generated.policy.namespaces.CreateNamespaceResponse; +import io.opentdf.platform.policy.namespaces.CreateNamespaceRequest; +import io.opentdf.platform.policy.namespaces.CreateNamespaceResponse; import io.opentdf.platform.sdk.*; import java.util.Collections; diff --git a/examples/src/main/java/io/opentdf/platform/CreateSubjectConditionSet.java b/examples/src/main/java/io/opentdf/platform/CreateSubjectConditionSet.java index 65eb18c0..5fbecfdd 100644 --- a/examples/src/main/java/io/opentdf/platform/CreateSubjectConditionSet.java +++ b/examples/src/main/java/io/opentdf/platform/CreateSubjectConditionSet.java @@ -1,14 +1,14 @@ package io.opentdf.platform; import com.connectrpc.ResponseMessageKt; -import io.opentdf.platform.generated.policy.Condition; -import io.opentdf.platform.generated.policy.ConditionBooleanTypeEnum; -import io.opentdf.platform.generated.policy.ConditionGroup; -import io.opentdf.platform.generated.policy.SubjectConditionSet; -import io.opentdf.platform.generated.policy.SubjectMappingOperatorEnum; -import io.opentdf.platform.generated.policy.SubjectSet; -import io.opentdf.platform.generated.policy.subjectmapping.CreateSubjectConditionSetRequest; -import io.opentdf.platform.generated.policy.subjectmapping.CreateSubjectConditionSetResponse; -import io.opentdf.platform.generated.policy.subjectmapping.SubjectConditionSetCreate; +import io.opentdf.platform.policy.Condition; +import io.opentdf.platform.policy.ConditionBooleanTypeEnum; +import io.opentdf.platform.policy.ConditionGroup; +import io.opentdf.platform.policy.SubjectConditionSet; +import io.opentdf.platform.policy.SubjectMappingOperatorEnum; +import io.opentdf.platform.policy.SubjectSet; +import io.opentdf.platform.policy.subjectmapping.CreateSubjectConditionSetRequest; +import io.opentdf.platform.policy.subjectmapping.CreateSubjectConditionSetResponse; +import io.opentdf.platform.policy.subjectmapping.SubjectConditionSetCreate; import io.opentdf.platform.sdk.*; import java.util.Collections; diff --git a/examples/src/main/java/io/opentdf/platform/CreateSubjectMapping.java b/examples/src/main/java/io/opentdf/platform/CreateSubjectMapping.java index f403b651..1370a38a 100644 --- a/examples/src/main/java/io/opentdf/platform/CreateSubjectMapping.java +++ b/examples/src/main/java/io/opentdf/platform/CreateSubjectMapping.java @@ -1,9 +1,9 @@ package io.opentdf.platform; import com.connectrpc.ResponseMessageKt; -import io.opentdf.platform.generated.policy.Action; -import io.opentdf.platform.generated.policy.SubjectMapping; -import io.opentdf.platform.generated.policy.subjectmapping.CreateSubjectMappingRequest; -import io.opentdf.platform.generated.policy.subjectmapping.CreateSubjectMappingResponse; +import io.opentdf.platform.policy.Action; +import io.opentdf.platform.policy.SubjectMapping; +import io.opentdf.platform.policy.subjectmapping.CreateSubjectMappingRequest; +import io.opentdf.platform.policy.subjectmapping.CreateSubjectMappingResponse; import io.opentdf.platform.sdk.*; import java.util.Collections; diff --git a/examples/src/main/java/io/opentdf/platform/GetDecisions.java b/examples/src/main/java/io/opentdf/platform/GetDecisions.java index 4a90e9f4..999f9ef3 100644 --- a/examples/src/main/java/io/opentdf/platform/GetDecisions.java +++ b/examples/src/main/java/io/opentdf/platform/GetDecisions.java @@ -1,13 +1,13 @@ package io.opentdf.platform; import com.connectrpc.ResponseMessageKt; -import io.opentdf.platform.generated.authorization.DecisionRequest; -import io.opentdf.platform.generated.authorization.DecisionResponse; -import io.opentdf.platform.generated.authorization.Entity; -import io.opentdf.platform.generated.authorization.EntityChain; -import io.opentdf.platform.generated.authorization.GetDecisionsRequest; -import io.opentdf.platform.generated.authorization.GetDecisionsResponse; -import io.opentdf.platform.generated.authorization.ResourceAttribute; -import io.opentdf.platform.generated.policy.Action; +import io.opentdf.platform.authorization.DecisionRequest; +import io.opentdf.platform.authorization.DecisionResponse; +import io.opentdf.platform.authorization.Entity; +import io.opentdf.platform.authorization.EntityChain; +import io.opentdf.platform.authorization.GetDecisionsRequest; +import io.opentdf.platform.authorization.GetDecisionsResponse; +import io.opentdf.platform.authorization.ResourceAttribute; +import io.opentdf.platform.policy.Action; import io.opentdf.platform.sdk.*; import java.util.Collections; diff --git a/examples/src/main/java/io/opentdf/platform/GetEntitlements.java b/examples/src/main/java/io/opentdf/platform/GetEntitlements.java index ef50fd49..e018bf9c 100644 --- a/examples/src/main/java/io/opentdf/platform/GetEntitlements.java +++ b/examples/src/main/java/io/opentdf/platform/GetEntitlements.java @@ -1,9 +1,9 @@ package io.opentdf.platform; import com.connectrpc.ResponseMessageKt; -import io.opentdf.platform.generated.authorization.Entity; -import io.opentdf.platform.generated.authorization.EntityEntitlements; -import io.opentdf.platform.generated.authorization.GetEntitlementsRequest; -import io.opentdf.platform.generated.authorization.GetEntitlementsResponse; +import io.opentdf.platform.authorization.Entity; +import io.opentdf.platform.authorization.EntityEntitlements; +import io.opentdf.platform.authorization.GetEntitlementsRequest; +import io.opentdf.platform.authorization.GetEntitlementsResponse; import io.opentdf.platform.sdk.*; import java.util.Collections; diff --git a/examples/src/main/java/io/opentdf/platform/ListAttributes.java b/examples/src/main/java/io/opentdf/platform/ListAttributes.java index 6d326e48..45528274 100644 --- a/examples/src/main/java/io/opentdf/platform/ListAttributes.java +++ b/examples/src/main/java/io/opentdf/platform/ListAttributes.java @@ -1,8 +1,8 @@ package io.opentdf.platform; import com.connectrpc.ResponseMessageKt; -import io.opentdf.platform.generated.policy.Attribute; -import io.opentdf.platform.generated.policy.attributes.ListAttributesRequest; -import io.opentdf.platform.generated.policy.attributes.ListAttributesResponse; +import io.opentdf.platform.policy.Attribute; +import io.opentdf.platform.policy.attributes.ListAttributesRequest; +import io.opentdf.platform.policy.attributes.ListAttributesResponse; import io.opentdf.platform.sdk.*; import java.util.Collections; diff --git a/examples/src/main/java/io/opentdf/platform/ListNamespaces.java b/examples/src/main/java/io/opentdf/platform/ListNamespaces.java index 93e37d6b..2f553d76 100644 --- a/examples/src/main/java/io/opentdf/platform/ListNamespaces.java +++ b/examples/src/main/java/io/opentdf/platform/ListNamespaces.java @@ -1,8 +1,8 @@ package io.opentdf.platform; import com.connectrpc.ResponseMessageKt; -import io.opentdf.platform.generated.policy.Namespace; -import io.opentdf.platform.generated.policy.namespaces.ListNamespacesRequest; -import io.opentdf.platform.generated.policy.namespaces.ListNamespacesResponse; +import io.opentdf.platform.policy.Namespace; +import io.opentdf.platform.policy.namespaces.ListNamespacesRequest; +import io.opentdf.platform.policy.namespaces.ListNamespacesResponse; import io.opentdf.platform.sdk.*; import java.util.Collections; diff --git a/examples/src/main/java/io/opentdf/platform/ListSubjectMappings.java b/examples/src/main/java/io/opentdf/platform/ListSubjectMappings.java index b92e0fca..e960cce7 100644 --- a/examples/src/main/java/io/opentdf/platform/ListSubjectMappings.java +++ b/examples/src/main/java/io/opentdf/platform/ListSubjectMappings.java @@ -1,8 +1,8 @@ package io.opentdf.platform; import com.connectrpc.ResponseMessageKt; -import io.opentdf.platform.generated.policy.SubjectMapping; -import io.opentdf.platform.generated.policy.subjectmapping.ListSubjectMappingsRequest; -import io.opentdf.platform.generated.policy.subjectmapping.ListSubjectMappingsResponse; +import io.opentdf.platform.policy.SubjectMapping; +import io.opentdf.platform.policy.subjectmapping.ListSubjectMappingsRequest; +import io.opentdf.platform.policy.subjectmapping.ListSubjectMappingsResponse; import io.opentdf.platform.sdk.*; import java.util.Collections; diff --git a/sdk/buf.gen.yaml b/sdk/buf.gen.yaml index a1640cdf..ba8ddec5 100644 --- a/sdk/buf.gen.yaml +++ b/sdk/buf.gen.yaml @@ -10,9 +10,9 @@ managed: module: buf.build/grpc-ecosystem/grpc-gateway override: - file_option: java_package_prefix - value: io.opentdf.platform.generated + value: io.opentdf.platform - file_option: java_package_prefix - value: io.opentdf.platform.generated.test + value: io.opentdf.platform.test module: buf.build/grpc/java plugins: - remote: buf.build/protocolbuffers/java:v25.3 diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/Autoconfigure.java b/sdk/src/main/java/io/opentdf/platform/sdk/Autoconfigure.java index 74d910eb..47d2964d 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/Autoconfigure.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/Autoconfigure.java @@ -1,16 +1,16 @@ package io.opentdf.platform.sdk; import com.connectrpc.ResponseMessageKt; -import io.opentdf.platform.generated.policy.Attribute; -import io.opentdf.platform.generated.policy.AttributeRuleTypeEnum; -import io.opentdf.platform.generated.policy.AttributeValueSelector; -import io.opentdf.platform.generated.policy.KasPublicKey; -import io.opentdf.platform.generated.policy.KasPublicKeyAlgEnum; -import io.opentdf.platform.generated.policy.KeyAccessServer; -import io.opentdf.platform.generated.policy.Value; -import io.opentdf.platform.generated.policy.attributes.AttributesServiceClient; -import io.opentdf.platform.generated.policy.attributes.GetAttributeValuesByFqnsRequest; -import io.opentdf.platform.generated.policy.attributes.GetAttributeValuesByFqnsResponse; +import io.opentdf.platform.policy.Attribute; +import io.opentdf.platform.policy.AttributeRuleTypeEnum; +import io.opentdf.platform.policy.AttributeValueSelector; +import io.opentdf.platform.policy.KasPublicKey; +import io.opentdf.platform.policy.KasPublicKeyAlgEnum; +import io.opentdf.platform.policy.KeyAccessServer; +import io.opentdf.platform.policy.Value; +import io.opentdf.platform.policy.attributes.AttributesServiceClient; +import io.opentdf.platform.policy.attributes.GetAttributeValuesByFqnsRequest; +import io.opentdf.platform.policy.attributes.GetAttributeValuesByFqnsResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/Config.java b/sdk/src/main/java/io/opentdf/platform/sdk/Config.java index d47e0b5f..a3bebc9a 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/Config.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/Config.java @@ -1,6 +1,6 @@ package io.opentdf.platform.sdk; -import io.opentdf.platform.generated.policy.Value; +import io.opentdf.platform.policy.Value; import io.opentdf.platform.sdk.Autoconfigure.AttributeValueFQN; import io.opentdf.platform.sdk.nanotdf.ECCMode; import io.opentdf.platform.sdk.nanotdf.Header; diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java b/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java index 2e09b461..3ea4347f 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java @@ -12,11 +12,11 @@ import com.nimbusds.jose.jwk.RSAKey; import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.SignedJWT; -import io.opentdf.platform.generated.kas.AccessServiceClient; -import io.opentdf.platform.generated.kas.PublicKeyRequest; -import io.opentdf.platform.generated.kas.PublicKeyResponse; -import io.opentdf.platform.generated.kas.RewrapRequest; -import io.opentdf.platform.generated.kas.RewrapResponse; +import io.opentdf.platform.kas.AccessServiceClient; +import io.opentdf.platform.kas.PublicKeyRequest; +import io.opentdf.platform.kas.PublicKeyResponse; +import io.opentdf.platform.kas.RewrapRequest; +import io.opentdf.platform.kas.RewrapResponse; import io.opentdf.platform.sdk.Config.KASInfo; import io.opentdf.platform.sdk.nanotdf.ECKeyPair; import io.opentdf.platform.sdk.nanotdf.NanoTDFType; diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/NanoTDF.java b/sdk/src/main/java/io/opentdf/platform/sdk/NanoTDF.java index c9554140..3ff8b4b5 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/NanoTDF.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/NanoTDF.java @@ -1,9 +1,9 @@ package io.opentdf.platform.sdk; import com.connectrpc.ResponseMessageKt; -import io.opentdf.platform.generated.policy.kasregistry.KeyAccessServerRegistryServiceClient; -import io.opentdf.platform.generated.policy.kasregistry.ListKeyAccessServersRequest; -import io.opentdf.platform.generated.policy.kasregistry.ListKeyAccessServersResponse; +import io.opentdf.platform.policy.kasregistry.KeyAccessServerRegistryServiceClient; +import io.opentdf.platform.policy.kasregistry.ListKeyAccessServersRequest; +import io.opentdf.platform.policy.kasregistry.ListKeyAccessServersResponse; import io.opentdf.platform.sdk.TDF.KasAllowlistException; import io.opentdf.platform.sdk.nanotdf.*; diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/SDK.java b/sdk/src/main/java/io/opentdf/platform/sdk/SDK.java index 3efb4e3a..5853ab73 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/SDK.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/SDK.java @@ -1,12 +1,12 @@ package io.opentdf.platform.sdk; import com.connectrpc.Interceptor; -import io.opentdf.platform.generated.authorization.AuthorizationServiceClient; -import io.opentdf.platform.generated.policy.attributes.AttributesServiceClient; -import io.opentdf.platform.generated.policy.kasregistry.KeyAccessServerRegistryServiceClient; -import io.opentdf.platform.generated.policy.namespaces.NamespaceServiceClient; -import io.opentdf.platform.generated.policy.resourcemapping.ResourceMappingServiceClient; -import io.opentdf.platform.generated.policy.subjectmapping.SubjectMappingServiceClient; +import io.opentdf.platform.authorization.AuthorizationServiceClient; +import io.opentdf.platform.policy.attributes.AttributesServiceClient; +import io.opentdf.platform.policy.kasregistry.KeyAccessServerRegistryServiceClient; +import io.opentdf.platform.policy.namespaces.NamespaceServiceClient; +import io.opentdf.platform.policy.resourcemapping.ResourceMappingServiceClient; +import io.opentdf.platform.policy.subjectmapping.SubjectMappingServiceClient; import io.opentdf.platform.sdk.nanotdf.NanoTDFType; import javax.net.ssl.TrustManager; diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java b/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java index da898927..6182d44f 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java @@ -24,15 +24,15 @@ import com.nimbusds.oauth2.sdk.token.TokenTypeURI; import com.nimbusds.oauth2.sdk.tokenexchange.TokenExchangeGrant; import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata; -import io.opentdf.platform.generated.authorization.AuthorizationServiceClient; -import io.opentdf.platform.generated.policy.attributes.AttributesServiceClient; -import io.opentdf.platform.generated.policy.kasregistry.KeyAccessServerRegistryServiceClient; -import io.opentdf.platform.generated.policy.namespaces.NamespaceServiceClient; -import io.opentdf.platform.generated.policy.resourcemapping.ResourceMappingServiceClient; -import io.opentdf.platform.generated.policy.subjectmapping.SubjectMappingServiceClient; -import io.opentdf.platform.generated.wellknownconfiguration.GetWellKnownConfigurationRequest; -import io.opentdf.platform.generated.wellknownconfiguration.GetWellKnownConfigurationResponse; -import io.opentdf.platform.generated.wellknownconfiguration.WellKnownServiceClient; +import io.opentdf.platform.authorization.AuthorizationServiceClient; +import io.opentdf.platform.policy.attributes.AttributesServiceClient; +import io.opentdf.platform.policy.kasregistry.KeyAccessServerRegistryServiceClient; +import io.opentdf.platform.policy.namespaces.NamespaceServiceClient; +import io.opentdf.platform.policy.resourcemapping.ResourceMappingServiceClient; +import io.opentdf.platform.policy.subjectmapping.SubjectMappingServiceClient; +import io.opentdf.platform.wellknownconfiguration.GetWellKnownConfigurationRequest; +import io.opentdf.platform.wellknownconfiguration.GetWellKnownConfigurationResponse; +import io.opentdf.platform.wellknownconfiguration.WellKnownServiceClient; import nl.altindag.ssl.SSLFactory; import nl.altindag.ssl.pem.util.PemUtils; import okhttp3.OkHttpClient; diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java b/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java index 30dfdae2..1d151015 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java @@ -5,11 +5,11 @@ import com.google.gson.GsonBuilder; import com.nimbusds.jose.*; -import io.opentdf.platform.generated.policy.Value; -import io.opentdf.platform.generated.policy.attributes.AttributesServiceClient; -import io.opentdf.platform.generated.policy.kasregistry.KeyAccessServerRegistryServiceClient; -import io.opentdf.platform.generated.policy.kasregistry.ListKeyAccessServersRequest; -import io.opentdf.platform.generated.policy.kasregistry.ListKeyAccessServersResponse; +import io.opentdf.platform.policy.Value; +import io.opentdf.platform.policy.attributes.AttributesServiceClient; +import io.opentdf.platform.policy.kasregistry.KeyAccessServerRegistryServiceClient; +import io.opentdf.platform.policy.kasregistry.ListKeyAccessServersRequest; +import io.opentdf.platform.policy.kasregistry.ListKeyAccessServersResponse; import io.opentdf.platform.sdk.Config.TDFConfig; import io.opentdf.platform.sdk.Autoconfigure.AttributeValueFQN; import io.opentdf.platform.sdk.Config.KASInfo; diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/AutoconfigureTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/AutoconfigureTest.java index 6f91f6e7..59cd0912 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/AutoconfigureTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/AutoconfigureTest.java @@ -13,18 +13,18 @@ import com.connectrpc.ResponseMessage; import com.connectrpc.UnaryBlockingCall; -import io.opentdf.platform.generated.policy.Attribute; -import io.opentdf.platform.generated.policy.AttributeRuleTypeEnum; -import io.opentdf.platform.generated.policy.KasPublicKey; -import io.opentdf.platform.generated.policy.KasPublicKeyAlgEnum; -import io.opentdf.platform.generated.policy.KasPublicKeySet; -import io.opentdf.platform.generated.policy.KeyAccessServer; -import io.opentdf.platform.generated.policy.Namespace; -import io.opentdf.platform.generated.policy.PublicKey; -import io.opentdf.platform.generated.policy.Value; -import io.opentdf.platform.generated.policy.attributes.AttributesServiceClient; -import io.opentdf.platform.generated.policy.attributes.GetAttributeValuesByFqnsRequest; -import io.opentdf.platform.generated.policy.attributes.GetAttributeValuesByFqnsResponse; +import io.opentdf.platform.policy.Attribute; +import io.opentdf.platform.policy.AttributeRuleTypeEnum; +import io.opentdf.platform.policy.KasPublicKey; +import io.opentdf.platform.policy.KasPublicKeyAlgEnum; +import io.opentdf.platform.policy.KasPublicKeySet; +import io.opentdf.platform.policy.KeyAccessServer; +import io.opentdf.platform.policy.Namespace; +import io.opentdf.platform.policy.PublicKey; +import io.opentdf.platform.policy.Value; +import io.opentdf.platform.policy.attributes.AttributesServiceClient; +import io.opentdf.platform.policy.attributes.GetAttributeValuesByFqnsRequest; +import io.opentdf.platform.policy.attributes.GetAttributeValuesByFqnsResponse; import io.opentdf.platform.sdk.Autoconfigure.AttributeValueFQN; import io.opentdf.platform.sdk.Autoconfigure.Granter.AttributeBooleanExpression; import io.opentdf.platform.sdk.Autoconfigure.Granter.BooleanKeyExpression; diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/KASClientTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/KASClientTest.java index 62ad5921..a6af98ff 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/KASClientTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/KASClientTest.java @@ -16,11 +16,11 @@ import io.grpc.Server; import io.grpc.ServerBuilder; import io.grpc.stub.StreamObserver; -import io.opentdf.platform.generated.kas.AccessServiceGrpc; -import io.opentdf.platform.generated.kas.PublicKeyRequest; -import io.opentdf.platform.generated.kas.PublicKeyResponse; -import io.opentdf.platform.generated.kas.RewrapRequest; -import io.opentdf.platform.generated.kas.RewrapResponse; +import io.opentdf.platform.kas.AccessServiceGrpc; +import io.opentdf.platform.kas.PublicKeyRequest; +import io.opentdf.platform.kas.PublicKeyResponse; +import io.opentdf.platform.kas.RewrapRequest; +import io.opentdf.platform.kas.RewrapResponse; import okhttp3.OkHttpClient; import okhttp3.Protocol; import org.junit.jupiter.api.Test; diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/NanoTDFTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/NanoTDFTest.java index 91835cd3..ff27be81 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/NanoTDFTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/NanoTDFTest.java @@ -2,10 +2,10 @@ import com.connectrpc.ResponseMessage; import com.connectrpc.UnaryBlockingCall; -import io.opentdf.platform.generated.policy.KeyAccessServer; -import io.opentdf.platform.generated.policy.kasregistry.KeyAccessServerRegistryServiceClient; -import io.opentdf.platform.generated.policy.kasregistry.ListKeyAccessServersRequest; -import io.opentdf.platform.generated.policy.kasregistry.ListKeyAccessServersResponse; +import io.opentdf.platform.policy.KeyAccessServer; +import io.opentdf.platform.policy.kasregistry.KeyAccessServerRegistryServiceClient; +import io.opentdf.platform.policy.kasregistry.ListKeyAccessServersRequest; +import io.opentdf.platform.policy.kasregistry.ListKeyAccessServersResponse; import io.opentdf.platform.sdk.Config.KASInfo; import io.opentdf.platform.sdk.Config.NanoTDFReaderConfig; import io.opentdf.platform.sdk.nanotdf.ECKeyPair; diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/SDKBuilderTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/SDKBuilderTest.java index a09275e5..f8262da6 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/SDKBuilderTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/SDKBuilderTest.java @@ -11,15 +11,15 @@ import io.grpc.ServerInterceptor; import io.grpc.StatusRuntimeException; import io.grpc.stub.StreamObserver; -import io.opentdf.platform.generated.kas.AccessServiceGrpc; -import io.opentdf.platform.generated.kas.RewrapRequest; -import io.opentdf.platform.generated.kas.RewrapResponse; -import io.opentdf.platform.generated.policy.namespaces.GetNamespaceRequest; -import io.opentdf.platform.generated.policy.namespaces.GetNamespaceResponse; -import io.opentdf.platform.generated.policy.namespaces.NamespaceServiceGrpc; -import io.opentdf.platform.generated.wellknownconfiguration.GetWellKnownConfigurationRequest; -import io.opentdf.platform.generated.wellknownconfiguration.GetWellKnownConfigurationResponse; -import io.opentdf.platform.generated.wellknownconfiguration.WellKnownServiceGrpc; +import io.opentdf.platform.kas.AccessServiceGrpc; +import io.opentdf.platform.kas.RewrapRequest; +import io.opentdf.platform.kas.RewrapResponse; +import io.opentdf.platform.policy.namespaces.GetNamespaceRequest; +import io.opentdf.platform.policy.namespaces.GetNamespaceResponse; +import io.opentdf.platform.policy.namespaces.NamespaceServiceGrpc; +import io.opentdf.platform.wellknownconfiguration.GetWellKnownConfigurationRequest; +import io.opentdf.platform.wellknownconfiguration.GetWellKnownConfigurationResponse; +import io.opentdf.platform.wellknownconfiguration.WellKnownServiceGrpc; import nl.altindag.ssl.SSLFactory; import nl.altindag.ssl.pem.util.PemUtils; import nl.altindag.ssl.util.KeyStoreUtils; diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java index 04a0b43b..83a3c3c0 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java @@ -3,10 +3,10 @@ import com.connectrpc.ResponseMessage; import com.connectrpc.UnaryBlockingCall; import com.nimbusds.jose.JOSEException; -import io.opentdf.platform.generated.policy.KeyAccessServer; -import io.opentdf.platform.generated.policy.kasregistry.KeyAccessServerRegistryServiceClient; -import io.opentdf.platform.generated.policy.kasregistry.ListKeyAccessServersRequest; -import io.opentdf.platform.generated.policy.kasregistry.ListKeyAccessServersResponse; +import io.opentdf.platform.policy.KeyAccessServer; +import io.opentdf.platform.policy.kasregistry.KeyAccessServerRegistryServiceClient; +import io.opentdf.platform.policy.kasregistry.ListKeyAccessServersRequest; +import io.opentdf.platform.policy.kasregistry.ListKeyAccessServersResponse; import io.opentdf.platform.sdk.Config.KASInfo; import io.opentdf.platform.sdk.TDF.Reader; import io.opentdf.platform.sdk.nanotdf.ECKeyPair; From 55bb7f458aa27935d972212bffcc485e3b38063e Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Thu, 8 May 2025 12:50:47 -0400 Subject: [PATCH 45/52] code review comments --- .../platform/sdk/AddressNormalizer.java | 35 ++++++++++++------- .../io/opentdf/platform/sdk/KASClient.java | 2 +- .../platform/sdk/AddressNormalizerTest.java | 6 ++-- 3 files changed, 27 insertions(+), 16 deletions(-) diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/AddressNormalizer.java b/sdk/src/main/java/io/opentdf/platform/sdk/AddressNormalizer.java index c84bc90c..0c682d1a 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/AddressNormalizer.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/AddressNormalizer.java @@ -15,30 +15,39 @@ private AddressNormalizer(){ } static String normalizeAddress(String urlString, boolean usePlaintext) { - URL url; + URI uri; try { - url = new URL(urlString); - } catch (MalformedURLException e) { - url = tryParseHostAndPort(urlString); + uri = new URI(urlString); + } catch (URISyntaxException e) { + throw new SDKException("error trying to parse URL [" + urlString + "]", e); + } + + if (uri.getHost() == null) { + // if there is no host then we are likely dealing with a host and port + try { + uri = new URI(usePlaintext ? "http" : "https", null, uri.getScheme(), Integer.parseInt(uri.getSchemeSpecificPart()), null, null, null); + } catch (URISyntaxException e) { + throw new SDKException("error trying to create URL for host and port[" + urlString + "]", e); + } } final int port; - if (url.getPort() == -1) { - port = "http".equals(url.getProtocol()) ? 80 : 443; + if (uri.getPort() == -1) { + port = usePlaintext ? 80 : 443; } else { - port = url.getPort(); + port = uri.getPort(); } - final String protocol = usePlaintext && "http".equals(url.getProtocol()) ? "http" : "https"; + final String scheme = usePlaintext ? "http" : "https"; try { - var returnUrl = new URL(protocol, url.getHost(), port, "").toString(); + var returnUrl = new URI(scheme, null, uri.getHost(), port, null, null, null).toString(); logger.debug("normalized url [{}] to [{}]", urlString, returnUrl); return returnUrl; - } catch (MalformedURLException e) { + } catch (URISyntaxException e) { throw new SDKException("error creating KAS address", e); } } - private static URL tryParseHostAndPort(String urlString) { + private static URI tryParseHostAndPort(String urlString) { URI uri; try { uri = new URI(null, urlString, null, null, null).parseServerAuthority(); @@ -47,8 +56,8 @@ private static URL tryParseHostAndPort(String urlString) { } try { - return new URL(uri.getPort() == 443 ? "https" : "http", uri.getHost(), uri.getPort(), ""); - } catch (MalformedURLException e) { + return new URI(uri.getPort() == 443 ? "https" : "http", null, uri.getHost(), uri.getPort(), "", "", ""); + } catch (URISyntaxException e) { throw new SDKException("error trying to create URL from host and port", e); } } diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java b/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java index 3ea4347f..dc852949 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java @@ -41,7 +41,7 @@ * This class provides methods to retrieve public keys, unwrap encrypted keys, * and manage key caches. */ -public class KASClient implements SDK.KAS { +class KASClient implements SDK.KAS { private final OkHttpClient httpClient; private final BiFunction protocolClientFactory; diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/AddressNormalizerTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/AddressNormalizerTest.java index 65794374..4afc0034 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/AddressNormalizerTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/AddressNormalizerTest.java @@ -10,16 +10,18 @@ class AddressNormalizerTest { @Test void testAddressNormalizationWithHTTPSClient() { - assertThat(normalizeAddress("http://example.org", false)).isEqualTo("https://example.org:80"); + assertThat(normalizeAddress("http://example.org", false)).isEqualTo("https://example.org:443"); // default to https if no scheme is provided assertThat(normalizeAddress("example.org:1234", false)).isEqualTo("https://example.org:1234"); + assertThat(normalizeAddress("ftp://example.org", false)).isEqualTo("https://example.org:443"); } @Test void testAddressNormaliationWithInsecureHTTPClient() { assertThat(normalizeAddress("http://localhost:8080", true)).isEqualTo("http://localhost:8080"); - assertThat(normalizeAddress("https://example.org", true)).isEqualTo("https://example.org:443"); + assertThat(normalizeAddress("http://example.org", true)).isEqualTo("http://example.org:80"); // default to http if no scheme is provided assertThat(normalizeAddress("example.org:1234", true)).isEqualTo("http://example.org:1234"); + assertThat(normalizeAddress("sftp://example.org", true)).isEqualTo("http://example.org:80"); } } \ No newline at end of file From b48663e817f4c2b62a73e84e7a8a348d3fb1f55c Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Thu, 8 May 2025 12:54:41 -0400 Subject: [PATCH 46/52] don't catch random --- sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java b/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java index dc852949..6d64ac9d 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/KASClient.java @@ -100,7 +100,7 @@ public Config.KASInfo getPublicKey(Config.KASInfo kasInfo) { PublicKeyResponse resp; try { resp = RequestHelper.getOrThrow(req); - } catch (Exception e) { + } catch (ConnectException e) { throw new SDKException("error getting public key", e); } @@ -197,9 +197,8 @@ public byte[] unwrap(Manifest.KeyAccess keyAccess, String policy, KeyType sessi throw new KasBadRequestException("rewrap request 400: " + e); } throw new SDKException("error unwrapping key", e); - } catch (Exception e) { - throw new SDKException("error unwrapping key", e); } + var wrappedKey = response.getEntityWrappedKey().toByteArray(); if (sessionKeyType != KeyType.RSA2048Key) { @@ -259,7 +258,7 @@ public byte[] unwrapNanoTDF(NanoTDFType.ECCurve curve, String header, String kas RewrapResponse response; try { response = RequestHelper.getOrThrow(request); - } catch (Exception e) { + } catch (ConnectException e) { throw new SDKException("error rewrapping key", e); } var wrappedKey = response.getEntityWrappedKey().toByteArray(); From bf6545c5de4a7ebe9efeb09918df2095175e2e27 Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Thu, 8 May 2025 13:35:31 -0400 Subject: [PATCH 47/52] sonar --- .../platform/sdk/AddressNormalizer.java | 21 ++----------------- .../java/io/opentdf/platform/sdk/NanoTDF.java | 1 - .../java/io/opentdf/platform/sdk/TDF.java | 3 --- .../java/io/opentdf/platform/sdk/TDFTest.java | 7 ++----- 4 files changed, 4 insertions(+), 28 deletions(-) diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/AddressNormalizer.java b/sdk/src/main/java/io/opentdf/platform/sdk/AddressNormalizer.java index 0c682d1a..8e616e9c 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/AddressNormalizer.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/AddressNormalizer.java @@ -3,10 +3,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; -import java.net.URL; class AddressNormalizer { private static final Logger logger = LoggerFactory.getLogger(AddressNormalizer.class); @@ -22,10 +20,11 @@ static String normalizeAddress(String urlString, boolean usePlaintext) { throw new SDKException("error trying to parse URL [" + urlString + "]", e); } + final String scheme = usePlaintext ? "http" : "https"; if (uri.getHost() == null) { // if there is no host then we are likely dealing with a host and port try { - uri = new URI(usePlaintext ? "http" : "https", null, uri.getScheme(), Integer.parseInt(uri.getSchemeSpecificPart()), null, null, null); + uri = new URI(scheme, null, uri.getScheme(), Integer.parseInt(uri.getSchemeSpecificPart()), null, null, null); } catch (URISyntaxException e) { throw new SDKException("error trying to create URL for host and port[" + urlString + "]", e); } @@ -36,7 +35,6 @@ static String normalizeAddress(String urlString, boolean usePlaintext) { } else { port = uri.getPort(); } - final String scheme = usePlaintext ? "http" : "https"; try { var returnUrl = new URI(scheme, null, uri.getHost(), port, null, null, null).toString(); @@ -46,19 +44,4 @@ static String normalizeAddress(String urlString, boolean usePlaintext) { throw new SDKException("error creating KAS address", e); } } - - private static URI tryParseHostAndPort(String urlString) { - URI uri; - try { - uri = new URI(null, urlString, null, null, null).parseServerAuthority(); - } catch (URISyntaxException e) { - throw new SDKException("error trying to parse host and port", e); - } - - try { - return new URI(uri.getPort() == 443 ? "https" : "http", null, uri.getHost(), uri.getPort(), "", "", ""); - } catch (URISyntaxException e) { - throw new SDKException("error trying to create URL from host and port", e); - } - } } diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/NanoTDF.java b/sdk/src/main/java/io/opentdf/platform/sdk/NanoTDF.java index 8116dc65..5b7bcf99 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/NanoTDF.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/NanoTDF.java @@ -1,7 +1,6 @@ package io.opentdf.platform.sdk; import com.connectrpc.ResponseMessageKt; -import io.opentdf.platform.policy.kasregistry.KeyAccessServerRegistryServiceClient; import io.opentdf.platform.policy.kasregistry.ListKeyAccessServersRequest; import io.opentdf.platform.policy.kasregistry.ListKeyAccessServersResponse; import io.opentdf.platform.sdk.TDF.KasAllowlistException; diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java b/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java index 14405415..1640fb79 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/TDF.java @@ -1,14 +1,11 @@ package io.opentdf.platform.sdk; import com.connectrpc.ConnectException; -import com.connectrpc.ResponseMessageKt; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.nimbusds.jose.*; import io.opentdf.platform.policy.Value; -import io.opentdf.platform.policy.attributes.AttributesServiceClient; -import io.opentdf.platform.policy.kasregistry.KeyAccessServerRegistryServiceClient; import io.opentdf.platform.policy.kasregistry.ListKeyAccessServersRequest; import io.opentdf.platform.policy.kasregistry.ListKeyAccessServersResponse; import io.opentdf.platform.sdk.Config.TDFConfig; diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java index cb28c184..eaa5929b 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/TDFTest.java @@ -11,7 +11,6 @@ import io.opentdf.platform.sdk.TDF.Reader; import io.opentdf.platform.sdk.nanotdf.ECKeyPair; import io.opentdf.platform.sdk.nanotdf.NanoTDFType; -import org.apache.commons.codec.DecoderException; import org.apache.commons.compress.utils.SeekableInMemoryByteChannel; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -22,12 +21,10 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.security.KeyPair; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; -import java.text.ParseException; import java.util.ArrayList; import java.util.Base64; import java.util.Collections; @@ -256,7 +253,7 @@ void testSimpleTDFWithAssertionWithRS256() throws Exception { keypair.getPrivate()); var rsaKasInfo = new Config.KASInfo(); - rsaKasInfo.URL = "https://example.com/kas"+Integer.toString(0); + rsaKasInfo.URL = "https://example.com/kas"+ 0; Config.TDFConfig config = Config.newTDFConfig( Config.withAutoconfigure(false), @@ -549,7 +546,7 @@ public void testCreateTDFWithMimeType() throws Exception { } @Test - void legacyTDFRoundTrips() throws DecoderException, IOException, JOSEException, ParseException, NoSuchAlgorithmException, URISyntaxException { + void legacyTDFRoundTrips() throws IOException, NoSuchAlgorithmException { final String mimeType = "application/pdf"; var assertionConfig1 = new AssertionConfig(); assertionConfig1.id = "assertion1"; From f2703c0f8db60db99e7c2523089e763c63a7b74b Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Thu, 8 May 2025 13:40:05 -0400 Subject: [PATCH 48/52] Update RequestHelper.kt --- sdk/src/main/kotlin/io/opentdf/platform/sdk/RequestHelper.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/src/main/kotlin/io/opentdf/platform/sdk/RequestHelper.kt b/sdk/src/main/kotlin/io/opentdf/platform/sdk/RequestHelper.kt index 957c3f0b..bf377157 100644 --- a/sdk/src/main/kotlin/io/opentdf/platform/sdk/RequestHelper.kt +++ b/sdk/src/main/kotlin/io/opentdf/platform/sdk/RequestHelper.kt @@ -18,4 +18,4 @@ class RequestHelper { return responseMessage.getOrThrow(); } } -} \ No newline at end of file +} From 0842b5e1434404b5e6f0e0ade629137799863fdf Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Thu, 8 May 2025 13:40:25 -0400 Subject: [PATCH 49/52] Update AddressNormalizerTest.java --- .../java/io/opentdf/platform/sdk/AddressNormalizerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/AddressNormalizerTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/AddressNormalizerTest.java index 4afc0034..079eb97a 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/AddressNormalizerTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/AddressNormalizerTest.java @@ -24,4 +24,4 @@ void testAddressNormaliationWithInsecureHTTPClient() { assertThat(normalizeAddress("example.org:1234", true)).isEqualTo("http://example.org:1234"); assertThat(normalizeAddress("sftp://example.org", true)).isEqualTo("http://example.org:80"); } -} \ No newline at end of file +} From cfae751a68fb29cc75934aaa63284bb2a4259e0c Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Thu, 8 May 2025 13:56:59 -0400 Subject: [PATCH 50/52] one more sonar --- .../opentdf/platform/sdk/Autoconfigure.java | 70 ++++++++++--------- 1 file changed, 36 insertions(+), 34 deletions(-) diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/Autoconfigure.java b/sdk/src/main/java/io/opentdf/platform/sdk/Autoconfigure.java index 47d2964d..8c5921b1 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/Autoconfigure.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/Autoconfigure.java @@ -711,45 +711,47 @@ public static Granter newGranterFromService(AttributesServiceClient as, KASKeyCa return getGranter(keyCache, new ArrayList<>(av.getFqnAttributeValuesMap().values())); } + private static List getGrants(GetAttributeValuesByFqnsResponse.AttributeAndValue attributeAndValue) { + var val = attributeAndValue.getValue(); + var attribute = attributeAndValue.getAttribute(); + String fqnstr = val.getFqn(); + AttributeValueFQN fqn = new AttributeValueFQN(fqnstr); + + if (!val.getGrantsList().isEmpty()) { + if (logger.isDebugEnabled()) { + logger.debug("adding grants from attribute value [{}]: {}", val.getFqn(), val.getGrantsList().stream().map(KeyAccessServer::getUri).collect(Collectors.toList())); + } + return val.getGrantsList(); + } else if (!attribute.getGrantsList().isEmpty()) { + var attributeGrants = attribute.getGrantsList(); + if (logger.isDebugEnabled()) { + logger.debug("adding grants from attribute [{}]: {}", attribute.getFqn(), attributeGrants.stream().map(KeyAccessServer::getId).collect(Collectors.toList())); + } + return attributeGrants; + } else if (!attribute.getNamespace().getGrantsList().isEmpty()) { + var nsGrants = attribute.getNamespace().getGrantsList(); + if (logger.isDebugEnabled()) { + logger.debug("adding grants from namespace [{}]: [{}]", attribute.getNamespace().getName(), nsGrants.stream().map(KeyAccessServer::getId).collect(Collectors.toList())); + } + return nsGrants; + } else { + // this is needed to mark the fact that we have an empty + logger.debug("didn't find any grants on value, attribute, or namespace for attribute value [{}]", fqnstr); + return Collections.emptyList(); + } + + } + private static Granter getGranter(@Nullable KASKeyCache keyCache, List values) { Granter grants = new Granter(values.stream().map(GetAttributeValuesByFqnsResponse.AttributeAndValue::getValue).map(Value::getFqn).map(AttributeValueFQN::new).collect(Collectors.toList())); for (var attributeAndValue: values) { - var val = attributeAndValue.getValue(); - var attribute = attributeAndValue.getAttribute(); - String fqnstr = val.getFqn(); + var attributeGrants = getGrants(attributeAndValue); + String fqnstr = attributeAndValue.getValue().getFqn(); AttributeValueFQN fqn = new AttributeValueFQN(fqnstr); - - if (!val.getGrantsList().isEmpty()) { - if (logger.isDebugEnabled()) { - logger.debug("adding grants from attribute value [{}]: {}", val.getFqn(), val.getGrantsList().stream().map(KeyAccessServer::getUri).collect(Collectors.toList())); - } - grants.addAllGrants(fqn, val.getGrantsList(), attribute); - if (keyCache != null) { - storeKeysToCache(val.getGrantsList(), keyCache); - } - } else if (!attribute.getGrantsList().isEmpty()) { - var attributeGrants = attribute.getGrantsList(); - if (logger.isDebugEnabled()) { - logger.debug("adding grants from attribute [{}]: {}", attribute.getFqn(), attributeGrants.stream().map(KeyAccessServer::getId).collect(Collectors.toList())); - } - grants.addAllGrants(fqn, attributeGrants, attribute); - if (keyCache != null) { - storeKeysToCache(attributeGrants, keyCache); - } - } else if (!attribute.getNamespace().getGrantsList().isEmpty()) { - var nsGrants = attribute.getNamespace().getGrantsList(); - if (logger.isDebugEnabled()) { - logger.debug("adding grants from namespace [{}]: [{}]", attribute.getNamespace().getName(), nsGrants.stream().map(KeyAccessServer::getId).collect(Collectors.toList())); - } - grants.addAllGrants(fqn, nsGrants, attribute); - if (keyCache != null) { - storeKeysToCache(nsGrants, keyCache); - } - } else { - // this is needed to mark the fact that we have an empty - grants.addAllGrants(fqn, List.of(), attribute); - logger.debug("didn't find any grants on value, attribute, or namespace for attribute value [{}]", fqnstr); + grants.addAllGrants(fqn, attributeGrants, attributeAndValue.getAttribute()); + if (keyCache != null) { + storeKeysToCache(attributeGrants, keyCache); } } From 06db4e994ae7771a9e1199b05f341d01c7af7096 Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Thu, 8 May 2025 14:01:14 -0400 Subject: [PATCH 51/52] one more sonar --- .../main/java/io/opentdf/platform/sdk/Autoconfigure.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/Autoconfigure.java b/sdk/src/main/java/io/opentdf/platform/sdk/Autoconfigure.java index 8c5921b1..fa4ded47 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/Autoconfigure.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/Autoconfigure.java @@ -714,8 +714,6 @@ public static Granter newGranterFromService(AttributesServiceClient as, KASKeyCa private static List getGrants(GetAttributeValuesByFqnsResponse.AttributeAndValue attributeAndValue) { var val = attributeAndValue.getValue(); var attribute = attributeAndValue.getAttribute(); - String fqnstr = val.getFqn(); - AttributeValueFQN fqn = new AttributeValueFQN(fqnstr); if (!val.getGrantsList().isEmpty()) { if (logger.isDebugEnabled()) { @@ -736,7 +734,9 @@ private static List getGrants(GetAttributeValuesByFqnsResponse. return nsGrants; } else { // this is needed to mark the fact that we have an empty - logger.debug("didn't find any grants on value, attribute, or namespace for attribute value [{}]", fqnstr); + if (logger.isDebugEnabled()) { + logger.debug("didn't find any grants on value, attribute, or namespace for attribute value [{}]", val.getFqn()); + } return Collections.emptyList(); } From eef248d06ba6a43a33616978d48abfc60833080e Mon Sep 17 00:00:00 2001 From: Morgan Kleene Date: Thu, 8 May 2025 15:53:11 -0400 Subject: [PATCH 52/52] Update sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java Co-authored-by: Dave Mihalcik --- sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java b/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java index 6182d44f..874e5352 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/SDKBuilder.java @@ -172,10 +172,9 @@ private Interceptor getAuthInterceptor(RSAKey rsaKey) { // we don't add the auth listener to this channel since it is only used to call // the well known endpoint - ProtocolClient bootstrapClient = null; GetWellKnownConfigurationResponse config; var httpClient = getHttpClient(); - bootstrapClient = getUnauthenticatedProtocolClient(platformEndpoint, httpClient) ; + ProtocolClient bootstrapClient = getUnauthenticatedProtocolClient(platformEndpoint, httpClient) ; var stub = new WellKnownServiceClient(bootstrapClient); try { config = RequestHelper.getOrThrow(stub.getWellKnownConfigurationBlocking(GetWellKnownConfigurationRequest.getDefaultInstance(), Collections.emptyMap()).execute());