1111import io .grpc .ServerCallHandler ;
1212import io .grpc .ServerInterceptor ;
1313import io .grpc .stub .StreamObserver ;
14+ import io .opentdf .platform .kas .AccessServiceGrpc ;
15+ import io .opentdf .platform .kas .RewrapRequest ;
16+ import io .opentdf .platform .kas .RewrapResponse ;
17+ import io .opentdf .platform .policy .namespaces .GetNamespaceRequest ;
18+ import io .opentdf .platform .policy .namespaces .GetNamespaceResponse ;
19+ import io .opentdf .platform .policy .namespaces .NamespaceServiceGrpc ;
1420import io .opentdf .platform .wellknownconfiguration .GetWellKnownConfigurationRequest ;
1521import io .opentdf .platform .wellknownconfiguration .GetWellKnownConfigurationResponse ;
1622import io .opentdf .platform .wellknownconfiguration .WellKnownServiceGrpc ;
1723import okhttp3 .mockwebserver .MockResponse ;
1824import okhttp3 .mockwebserver .MockWebServer ;
25+ import org .junit .jupiter .api .AfterAll ;
26+ import org .junit .jupiter .api .BeforeAll ;
1927import org .junit .jupiter .api .Test ;
2028
2129import java .io .IOException ;
3038public class SDKBuilderTest {
3139
3240 @ Test
33- void testCreatingSDKChannel () throws IOException , InterruptedException {
34- Server wellknownServer = null ;
41+ void testCreatingSDKServices () throws IOException , InterruptedException {
42+ Server platformServicesServer = null ;
43+ Server kasServer = null ;
3544 // we use the HTTP server for two things:
3645 // * it returns the OIDC configuration we use at bootstrapping time
3746 // * it fakes out being an IDP and returns an access token when need to retrieve an access token
@@ -51,6 +60,8 @@ void testCreatingSDKChannel() throws IOException, InterruptedException {
5160 .setHeader ("Content-type" , "application/json" )
5261 );
5362
63+ // this service returns the platform_issuer url to the SDK during bootstrapping. This
64+ // tells the SDK where to download the OIDC discovery document from (our test webserver!)
5465 WellKnownServiceGrpc .WellKnownServiceImplBase wellKnownService = new WellKnownServiceGrpc .WellKnownServiceImplBase () {
5566 @ Override
5667 public void getWellKnownConfiguration (GetWellKnownConfigurationRequest request , StreamObserver <GetWellKnownConfigurationResponse > responseObserver ) {
@@ -65,55 +76,76 @@ public void getWellKnownConfiguration(GetWellKnownConfigurationRequest request,
6576 }
6677 };
6778
68- AtomicReference <String > authHeaderFromRequest = new AtomicReference <>(null );
69- AtomicReference <String > dpopHeaderFromRequest = new AtomicReference <>(null );
79+ // remember the auth headers that we received during GRPC calls to platform services
80+ AtomicReference <String > servicesAuthHeader = new AtomicReference <>(null );
81+ AtomicReference <String > servicesDPoPHeader = new AtomicReference <>(null );
7082
83+ // remember the auth headers that we received during GRPC calls to KAS
84+ AtomicReference <String > kasAuthHeader = new AtomicReference <>(null );
85+ AtomicReference <String > kasDPoPHeader = new AtomicReference <>(null );
7186 // we use the server in two different ways. the first time we use it to actually return
7287 // issuer for bootstrapping. the second time we use the interception functionality in order
7388 // to make sure that we are including a DPoP proof and an auth header
74- int randomPort ;
75- try (ServerSocket socket = new ServerSocket (0 )) {
76- randomPort = socket .getLocalPort ();
77- }
78- wellknownServer = ServerBuilder
79- .forPort (randomPort )
89+ platformServicesServer = ServerBuilder
90+ .forPort (getRandomPort ())
8091 .directExecutor ()
8192 .addService (wellKnownService )
93+ .addService (new NamespaceServiceGrpc .NamespaceServiceImplBase () {})
94+ .intercept (new ServerInterceptor () {
95+ @ Override
96+ public <ReqT , RespT > ServerCall .Listener <ReqT > interceptCall (ServerCall <ReqT , RespT > call , Metadata headers , ServerCallHandler <ReqT , RespT > next ) {
97+ servicesAuthHeader .set (headers .get (Metadata .Key .of ("Authorization" , Metadata .ASCII_STRING_MARSHALLER )));
98+ servicesDPoPHeader .set (headers .get (Metadata .Key .of ("DPoP" , Metadata .ASCII_STRING_MARSHALLER )));
99+ return next .startCall (call , headers );
100+ }
101+ })
102+ .build ()
103+ .start ();
104+
105+
106+ kasServer = ServerBuilder
107+ .forPort (getRandomPort ())
108+ .directExecutor ()
109+ .addService (new AccessServiceGrpc .AccessServiceImplBase () {
110+ @ Override
111+ public void rewrap (RewrapRequest request , StreamObserver <RewrapResponse > responseObserver ) {
112+ responseObserver .onNext (RewrapResponse .getDefaultInstance ());
113+ responseObserver .onCompleted ();
114+ }
115+ })
82116 .intercept (new ServerInterceptor () {
83117 @ Override
84118 public <ReqT , RespT > ServerCall .Listener <ReqT > interceptCall (ServerCall <ReqT , RespT > call , Metadata headers , ServerCallHandler <ReqT , RespT > next ) {
85- authHeaderFromRequest .set (headers .get (Metadata .Key .of ("Authorization" , Metadata .ASCII_STRING_MARSHALLER )));
86- dpopHeaderFromRequest .set (headers .get (Metadata .Key .of ("DPoP" , Metadata .ASCII_STRING_MARSHALLER )));
119+ kasAuthHeader .set (headers .get (Metadata .Key .of ("Authorization" , Metadata .ASCII_STRING_MARSHALLER )));
120+ kasDPoPHeader .set (headers .get (Metadata .Key .of ("DPoP" , Metadata .ASCII_STRING_MARSHALLER )));
87121 return next .startCall (call , headers );
88122 }
89123 })
90124 .build ()
91125 .start ();
92126
93- ManagedChannel channel = SDKBuilder
127+ SDK . Services services = SDKBuilder
94128 .newBuilder ()
95129 .clientSecret ("client-id" , "client-secret" )
96- .platformEndpoint ("localhost:" + wellknownServer .getPort ())
130+ .platformEndpoint ("localhost:" + platformServicesServer .getPort ())
97131 .useInsecurePlaintextConnection (true )
98- .buildChannel ();
99-
100- assertThat (channel ).isNotNull ();
101- assertThat (channel .getState (false )).isEqualTo (ConnectivityState .IDLE );
132+ .buildServices ();
102133
103- var wellKnownStub = WellKnownServiceGrpc . newBlockingStub ( channel );
134+ assertThat ( services ). isNotNull ( );
104135
105136 httpServer .enqueue (new MockResponse ()
106137 .setBody ("{\" access_token\" : \" hereisthetoken\" , \" token_type\" : \" Bearer\" }" )
107138 .setHeader ("Content-Type" , "application/json" ));
108139
109- var ignored = wellKnownStub .getWellKnownConfiguration (GetWellKnownConfigurationRequest .getDefaultInstance ());
110- channel .shutdownNow ();
140+ var ignored = services .namespaces ().getNamespace (GetNamespaceRequest .getDefaultInstance ());
111141
112142 // we've now made two requests. one to get the bootstrapping info and one
113143 // call that should activate the token fetching logic
114144 assertThat (httpServer .getRequestCount ()).isEqualTo (2 );
115145
116146 httpServer .takeRequest ();
147+
148+ // validate that we made a reasonable request to our fake IdP to get an access token
117149 var accessTokenRequest = httpServer .takeRequest ();
118150 assertThat (accessTokenRequest ).isNotNull ();
119151 var authHeader = accessTokenRequest .getHeader ("Authorization" );
@@ -124,16 +156,35 @@ public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, Re
124156 var usernameAndPassword = new String (Base64 .getDecoder ().decode (authHeaderParts [1 ]), StandardCharsets .UTF_8 );
125157 assertThat (usernameAndPassword ).isEqualTo ("client-id:client-secret" );
126158
127- assertThat (dpopHeaderFromRequest .get ()).isNotNull ();
128- assertThat (authHeaderFromRequest .get ()).isEqualTo ("DPoP hereisthetoken" );
159+ // validate that during the request to the namespace service we supplied a valid token
160+ assertThat (servicesDPoPHeader .get ()).isNotNull ();
161+ assertThat (servicesAuthHeader .get ()).isEqualTo ("DPoP hereisthetoken" );
129162
130163 var body = new String (accessTokenRequest .getBody ().readByteArray (), StandardCharsets .UTF_8 );
131164 assertThat (body ).contains ("grant_type=client_credentials" );
132165
166+ // now call KAS _on a different server_ and make sure that the interceptors provide us with auth tokens
167+ int kasPort = kasServer .getPort ();
168+ SDK .KASInfo kasInfo = () -> "localhost:" + kasPort ;
169+ services .kas ().unwrap (kasInfo , new SDK .Policy () {});
170+
171+ assertThat (kasDPoPHeader .get ()).isNotNull ();
172+ assertThat (kasAuthHeader .get ()).isEqualTo ("DPoP hereisthetoken" );
133173 } finally {
134- if (wellknownServer != null ) {
135- wellknownServer .shutdownNow ();
174+ if (platformServicesServer != null ) {
175+ platformServicesServer .shutdownNow ();
176+ }
177+ if (kasServer != null ) {
178+ kasServer .shutdownNow ();
136179 }
137180 }
138181 }
182+
183+ private static int getRandomPort () throws IOException {
184+ int randomPort ;
185+ try (ServerSocket socket = new ServerSocket (0 )) {
186+ randomPort = socket .getLocalPort ();
187+ }
188+ return randomPort ;
189+ }
139190}
0 commit comments