package persistent import ( "bytes" "context" "crypto/ecdsa" "crypto/elliptic" "path/filepath" "testing" "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs" "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/session" usertest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user/test" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/stretchr/testify/require" "go.etcd.io/bbolt" ) func TestTokenStore(t *testing.T) { ts, err := NewTokenStore(filepath.Join(t.TempDir(), ".storage")) require.NoError(t, err) defer ts.Close() owner := *usertest.ID() var ownerV2 refs.OwnerID owner.WriteToV2(&ownerV2) req := new(session.CreateRequestBody) req.SetOwnerID(&ownerV2) const tokenNumber = 5 type tok struct { id []byte key []byte } tokens := make([]tok, 0, tokenNumber) for i := 0; i < tokenNumber; i++ { req.SetExpiration(uint64(i)) res, err := ts.Create(context.Background(), req) require.NoError(t, err) tokens = append(tokens, tok{ id: res.GetID(), key: res.GetSessionKey(), }) } for i, token := range tokens { savedToken := ts.Get(owner, token.id) require.Equal(t, uint64(i), savedToken.ExpiredAt()) equalKeys(t, token.key, savedToken.SessionKey()) } } func TestTokenStore_Persistent(t *testing.T) { path := filepath.Join(t.TempDir(), ".storage") ts, err := NewTokenStore(path) require.NoError(t, err) idOwner := *usertest.ID() var idOwnerV2 refs.OwnerID idOwner.WriteToV2(&idOwnerV2) const exp = 12345 req := new(session.CreateRequestBody) req.SetOwnerID(&idOwnerV2) req.SetExpiration(exp) res, err := ts.Create(context.Background(), req) require.NoError(t, err) id := res.GetID() pubKey := res.GetSessionKey() // close db (stop the node) require.NoError(t, ts.Close()) // open persistent storage again ts, err = NewTokenStore(path) require.NoError(t, err) defer ts.Close() savedToken := ts.Get(idOwner, id) equalKeys(t, pubKey, savedToken.SessionKey()) } func TestTokenStore_RemoveOld(t *testing.T) { tests := []*struct { epoch uint64 id, key []byte }{ { epoch: 1, }, { epoch: 2, }, { epoch: 3, }, { epoch: 4, }, { epoch: 5, }, { epoch: 6, }, } ts, err := NewTokenStore(filepath.Join(t.TempDir(), ".storage")) require.NoError(t, err) defer ts.Close() owner := *usertest.ID() var ownerV2 refs.OwnerID owner.WriteToV2(&ownerV2) req := new(session.CreateRequestBody) req.SetOwnerID(&ownerV2) for _, test := range tests { req.SetExpiration(test.epoch) res, err := ts.Create(context.Background(), req) require.NoError(t, err) test.id = res.GetID() test.key = res.GetSessionKey() } const currEpoch = 3 ts.RemoveOld(currEpoch) for _, test := range tests { token := ts.Get(owner, test.id) if test.epoch <= currEpoch { require.Nil(t, token) } else { equalKeys(t, test.key, token.SessionKey()) } } } // This test was added to fix bolt's behaviour since the persistent // storage uses cursor and there is an issue about `cursor.Delete` // method: https://github.com/etcd-io/bbolt/issues/146. // // If this test is passing, TokenStore works correctly. func TestBolt_Cursor(t *testing.T) { db, err := bbolt.Open(filepath.Join(t.TempDir(), ".storage"), 0o666, nil) require.NoError(t, err) defer db.Close() cursorKeys := make(map[string]struct{}) bucketName := []byte("bucket") err = db.Update(func(tx *bbolt.Tx) (err error) { b, err := tx.CreateBucket(bucketName) if err != nil { return err } put := func(s []byte) { if err == nil { err = b.Put(s, s) } } put([]byte("1")) put([]byte("2")) put([]byte("3")) put([]byte("4")) return }) err = db.Update(func(tx *bbolt.Tx) error { b := tx.Bucket(bucketName) c := b.Cursor() for k, _ := c.First(); k != nil; k, _ = c.Next() { // fill key that was viewed cursorKeys[string(k)] = struct{}{} if bytes.Equal(k, []byte("1")) { // delete the first one err = c.Delete() if err != nil { return err } } } return nil }) require.NoError(t, err) _, ok := cursorKeys["2"] if !ok { t.Fatal("unexpectedly skipped '2' value") } } func equalKeys(t *testing.T, sessionKey []byte, savedPrivateKey *ecdsa.PrivateKey) { returnedPubKey, err := keys.NewPublicKeyFromBytes(sessionKey, elliptic.P256()) require.NoError(t, err) savedPubKey := (keys.PublicKey)(savedPrivateKey.PublicKey) require.Equal(t, true, returnedPubKey.Equal(&savedPubKey)) }