@@ -213,6 +213,137 @@ class GatewaySessionInvokeTest {
213213 }
214214 }
215215
216+ @Test
217+ fun connect_storesPrimaryDeviceTokenFromSuccessfulSharedTokenConnect () = runBlocking {
218+ val json = testJson()
219+ val connected = CompletableDeferred <Unit >()
220+ val lastDisconnect = AtomicReference (" " )
221+ val server =
222+ startGatewayServer(json) { webSocket, id, method, _ ->
223+ when (method) {
224+ " connect" -> {
225+ webSocket.send(
226+ connectResponseFrame(
227+ id,
228+ authJson = """ {"deviceToken":"shared-node-token","role":"node","scopes":[]}""" ,
229+ ),
230+ )
231+ webSocket.close(1000 , " done" )
232+ }
233+ }
234+ }
235+
236+ val harness =
237+ createNodeHarness(
238+ connected = connected,
239+ lastDisconnect = lastDisconnect,
240+ ) { GatewaySession .InvokeResult .ok(""" {"handled":true}""" ) }
241+
242+ try {
243+ connectNodeSession(
244+ session = harness.session,
245+ port = server.port,
246+ token = " shared-auth-token" ,
247+ bootstrapToken = null ,
248+ )
249+ awaitConnectedOrThrow(connected, lastDisconnect, server)
250+
251+ val deviceId = DeviceIdentityStore (RuntimeEnvironment .getApplication()).loadOrCreate().deviceId
252+ assertEquals(" shared-node-token" , harness.deviceAuthStore.loadToken(deviceId, " node" ))
253+ assertNull(harness.deviceAuthStore.loadToken(deviceId, " operator" ))
254+ } finally {
255+ shutdownHarness(harness, server)
256+ }
257+ }
258+
259+ @Test
260+ fun bootstrapConnect_storesAdditionalBoundedDeviceTokensOnTrustedTransport () = runBlocking {
261+ val json = testJson()
262+ val connected = CompletableDeferred <Unit >()
263+ val lastDisconnect = AtomicReference (" " )
264+ val server =
265+ startGatewayServer(json) { webSocket, id, method, _ ->
266+ when (method) {
267+ " connect" -> {
268+ webSocket.send(
269+ connectResponseFrame(
270+ id,
271+ authJson =
272+ """ {"deviceToken":"bootstrap-node-token","role":"node","scopes":[],"deviceTokens":[{"deviceToken":"bootstrap-operator-token","role":"operator","scopes":["operator.admin","operator.approvals","operator.read","operator.talk.secrets","operator.write"]}]}""" ,
273+ ),
274+ )
275+ webSocket.close(1000 , " done" )
276+ }
277+ }
278+ }
279+
280+ val harness =
281+ createNodeHarness(
282+ connected = connected,
283+ lastDisconnect = lastDisconnect,
284+ ) { GatewaySession .InvokeResult .ok(""" {"handled":true}""" ) }
285+
286+ try {
287+ connectNodeSession(
288+ session = harness.session,
289+ port = server.port,
290+ token = null ,
291+ bootstrapToken = " bootstrap-token" ,
292+ )
293+ awaitConnectedOrThrow(connected, lastDisconnect, server)
294+
295+ val deviceId = DeviceIdentityStore (RuntimeEnvironment .getApplication()).loadOrCreate().deviceId
296+ assertEquals(" bootstrap-node-token" , harness.deviceAuthStore.loadToken(deviceId, " node" ))
297+ assertEquals(" bootstrap-operator-token" , harness.deviceAuthStore.loadToken(deviceId, " operator" ))
298+ } finally {
299+ shutdownHarness(harness, server)
300+ }
301+ }
302+
303+ @Test
304+ fun nonBootstrapConnect_ignoresAdditionalBootstrapDeviceTokens () = runBlocking {
305+ val json = testJson()
306+ val connected = CompletableDeferred <Unit >()
307+ val lastDisconnect = AtomicReference (" " )
308+ val server =
309+ startGatewayServer(json) { webSocket, id, method, _ ->
310+ when (method) {
311+ " connect" -> {
312+ webSocket.send(
313+ connectResponseFrame(
314+ id,
315+ authJson =
316+ """ {"deviceToken":"shared-node-token","role":"node","scopes":[],"deviceTokens":[{"deviceToken":"shared-operator-token","role":"operator","scopes":["operator.approvals","operator.read"]}]}""" ,
317+ ),
318+ )
319+ webSocket.close(1000 , " done" )
320+ }
321+ }
322+ }
323+
324+ val harness =
325+ createNodeHarness(
326+ connected = connected,
327+ lastDisconnect = lastDisconnect,
328+ ) { GatewaySession .InvokeResult .ok(""" {"handled":true}""" ) }
329+
330+ try {
331+ connectNodeSession(
332+ session = harness.session,
333+ port = server.port,
334+ token = " shared-auth-token" ,
335+ bootstrapToken = null ,
336+ )
337+ awaitConnectedOrThrow(connected, lastDisconnect, server)
338+
339+ val deviceId = DeviceIdentityStore (RuntimeEnvironment .getApplication()).loadOrCreate().deviceId
340+ assertEquals(" shared-node-token" , harness.deviceAuthStore.loadToken(deviceId, " node" ))
341+ assertNull(harness.deviceAuthStore.loadToken(deviceId, " operator" ))
342+ } finally {
343+ shutdownHarness(harness, server)
344+ }
345+ }
346+
216347 @Test
217348 fun nodeInvokeRequest_roundTripsInvokeResult () = runBlocking {
218349 val handshakeOrigin = AtomicReference <String ?>(null )
@@ -470,9 +601,14 @@ class GatewaySessionInvokeTest {
470601 }
471602 }
472603
473- private fun connectResponseFrame (id : String , canvasHostUrl : String? = null): String {
604+ private fun connectResponseFrame (
605+ id : String ,
606+ canvasHostUrl : String? = null,
607+ authJson : String? = null,
608+ ): String {
474609 val canvas = canvasHostUrl?.let { " \" canvasHostUrl\" :\" $it \" ," } ? : " "
475- return """ {"type":"res","id":"$id ","ok":true,"payload":{$canvas "snapshot":{"sessionDefaults":{"mainSessionKey":"main"}}}}"""
610+ val auth = authJson?.let { " \" auth\" :$it ," } ? : " "
611+ return """ {"type":"res","id":"$id ","ok":true,"payload":{$canvas$auth "snapshot":{"sessionDefaults":{"mainSessionKey":"main"}}}}"""
476612 }
477613
478614 private fun startGatewayServer (
0 commit comments