@@ -352,6 +352,115 @@ func TestSessionsStoreMigration(t *testing.T) {
352
352
return getBoltStoreSessions (t , store )
353
353
},
354
354
},
355
+ {
356
+ name : "multiple sessions with the same ID" ,
357
+ populateDB : func (t * testing.T , store * BoltStore ,
358
+ _ accounts.Store ) []* Session {
359
+
360
+ // We first add one session which has no other
361
+ // session with same ID, to test that this is
362
+ // correctly migrated, and included in the
363
+ // migration result.
364
+ sess1 , err := store .NewSession (
365
+ ctx , "session1" , TypeMacaroonAdmin ,
366
+ time .Unix (1000 , 0 ), "" ,
367
+ )
368
+ require .NoError (t , err )
369
+
370
+ sess2 , err := store .NewSession (
371
+ ctx , "session2" , TypeMacaroonAdmin ,
372
+ time .Unix (1000 , 0 ), "" ,
373
+ )
374
+ require .NoError (t , err )
375
+
376
+ // Then add two sessions with the same ID, to
377
+ // test that only the latest session is included
378
+ // in the migration result.
379
+ sess3 , err := store .NewSession (
380
+ ctx , "session3" , TypeMacaroonAdmin ,
381
+ time .Unix (1000 , 0 ), "" ,
382
+ )
383
+ require .NoError (t , err )
384
+
385
+ // During the addition of the session linking
386
+ // functionality, logic was added in the
387
+ // NewSession function to ensure we can't create
388
+ // multiple sessions with the same ID. Therefore
389
+ // we need to manually override the ID of
390
+ // the second session to match the first
391
+ // session, to simulate such a scenario that
392
+ // could occur prior to the addition of that
393
+ // logic.
394
+ // We also need to update the CreatedAt time
395
+ // as the execution of this function is too
396
+ // fast for the CreatedAt time of sess2 and
397
+ // sess3 to differ.
398
+ err = updateSessionIDAndCreatedAt (
399
+ store , sess3 .ID , sess2 .MacaroonRootKey ,
400
+ sess2 .CreatedAt .Add (time .Minute ),
401
+ )
402
+ require .NoError (t , err )
403
+
404
+ // Finally, we add three sessions with the same
405
+ // ID, to test we can handle more than two
406
+ // sessions with the same ID.
407
+ sess4 , err := store .NewSession (
408
+ ctx , "session4" , TypeMacaroonAdmin ,
409
+ time .Unix (1000 , 0 ), "" ,
410
+ )
411
+ require .NoError (t , err )
412
+
413
+ sess5 , err := store .NewSession (
414
+ ctx , "session5" , TypeMacaroonAdmin ,
415
+ time .Unix (1000 , 0 ), "" ,
416
+ )
417
+ require .NoError (t , err )
418
+
419
+ sess6 , err := store .NewSession (
420
+ ctx , "session6" , TypeMacaroonAdmin ,
421
+ time .Unix (1000 , 0 ), "" ,
422
+ )
423
+ require .NoError (t , err )
424
+
425
+ err = updateSessionIDAndCreatedAt (
426
+ store , sess5 .ID , sess4 .MacaroonRootKey ,
427
+ sess2 .CreatedAt .Add (time .Minute ),
428
+ )
429
+ require .NoError (t , err )
430
+
431
+ err = updateSessionIDAndCreatedAt (
432
+ store , sess6 .ID , sess4 .MacaroonRootKey ,
433
+ sess2 .CreatedAt .Add (time .Minute * 2 ),
434
+ )
435
+ require .NoError (t , err )
436
+
437
+ // Now fetch the updated sessions from the kv
438
+ // store, so that we are sure that the new IDs
439
+ // have really been persisted in the DB.
440
+ kvSessions := getBoltStoreSessions (t , store )
441
+ require .Len (t , kvSessions , 6 )
442
+
443
+ getSessionByName := func (name string ) * Session {
444
+ for _ , session := range kvSessions {
445
+ if session .Label == name {
446
+ return session
447
+ }
448
+ }
449
+
450
+ t .Fatalf ("session %s not found" , name )
451
+ return nil
452
+ }
453
+
454
+ // When multiple sessions with the same ID
455
+ // exist, we expect only the session with the
456
+ // latest creation time to be migrated.
457
+ return []* Session {
458
+ getSessionByName (sess1 .Label ),
459
+ getSessionByName (sess3 .Label ),
460
+ getSessionByName (sess6 .Label ),
461
+ }
462
+ },
463
+ },
355
464
{
356
465
name : "randomized sessions" ,
357
466
populateDB : randomizedSessions ,
@@ -803,3 +912,49 @@ func shiftStateUnsafe(db *BoltStore, id ID, dest State) error {
803
912
return putSession (sessionBucket , session )
804
913
})
805
914
}
915
+
916
+ // updateSessionIDAndCreatedAt can be used to update the ID, the GroupID,
917
+ // the MacaroonRootKey and the CreatedAt time a session in the BoltStore.
918
+ //
919
+ // NOTE: this function should only be used for testing purposes. Also note that
920
+ // we pass the macaroon root key to set the new session ID, as the
921
+ // DeserializeSession function derives the session ID from the
922
+ // session.MacaroonRootKey.
923
+ func updateSessionIDAndCreatedAt (db * BoltStore , oldID ID , newIdRootKey uint64 ,
924
+ newCreatedAt time.Time ) error {
925
+
926
+ newId := IDFromMacRootKeyID (newIdRootKey )
927
+
928
+ if oldID == newId {
929
+ return fmt .Errorf ("can't update session ID to the same ID: %s" ,
930
+ oldID )
931
+ }
932
+
933
+ return db .Update (func (tx * bbolt.Tx ) error {
934
+ // Get the main session bucket.
935
+ sessionBkt , err := getBucket (tx , sessionBucketKey )
936
+ if err != nil {
937
+ return err
938
+ }
939
+
940
+ // Look up the session using the old ID.
941
+ sess , err := getSessionByID (sessionBkt , oldID )
942
+ if err != nil {
943
+ return err
944
+ }
945
+
946
+ // Delete the old serialized session (keyed by local pubkey).
947
+ if err := sessionBkt .Delete (getSessionKey (sess )); err != nil {
948
+ return err
949
+ }
950
+
951
+ // Update the session ID.
952
+ sess .ID = newId
953
+ sess .GroupID = newId
954
+ sess .MacaroonRootKey = newIdRootKey
955
+ sess .CreatedAt = newCreatedAt
956
+
957
+ // Write it back under the same key (local pubkey).
958
+ return putSession (sessionBkt , sess )
959
+ })
960
+ }
0 commit comments