@@ -352,6 +352,115 @@ func TestSessionsStoreMigration(t *testing.T) {
352352 return getBoltStoreSessions (t , store )
353353 },
354354 },
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+ sess4 .CreatedAt .Add (time .Minute ),
428+ )
429+ require .NoError (t , err )
430+
431+ err = updateSessionIDAndCreatedAt (
432+ store , sess6 .ID , sess4 .MacaroonRootKey ,
433+ sess4 .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+ },
355464 {
356465 name : "randomized sessions" ,
357466 populateDB : randomizedSessions ,
@@ -803,3 +912,44 @@ func shiftStateUnsafe(db *BoltStore, id ID, dest State) error {
803912 return putSession (sessionBucket , session )
804913 })
805914}
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+ // Update the session.
947+ sess .ID = newId
948+ sess .GroupID = newId
949+ sess .MacaroonRootKey = newIdRootKey
950+ sess .CreatedAt = newCreatedAt
951+
952+ // Write it back under the same key (local pubkey).
953+ return putSession (sessionBkt , sess )
954+ })
955+ }
0 commit comments