From 98291265605caccadf9f6142a78a9574a7606252 Mon Sep 17 00:00:00 2001 From: Luisfc68 Date: Thu, 5 Dec 2024 12:38:03 +0100 Subject: [PATCH] feat: add quote hash validation inside repository --- .../dataproviders/database/mongo/pegin.go | 8 ++++ .../database/mongo/pegin_test.go | 30 ++++++++++---- .../dataproviders/database/mongo/pegout.go | 12 +++++- .../database/mongo/pegout_test.go | 41 +++++++++++++++---- .../rest/server/cookies/store_test.go | 4 +- .../watcher/pegin_btc_deposit_watcher_test.go | 2 +- test/utils.go | 2 +- 7 files changed, 78 insertions(+), 21 deletions(-) diff --git a/internal/adapters/dataproviders/database/mongo/pegin.go b/internal/adapters/dataproviders/database/mongo/pegin.go index 89565f55..f033d01f 100644 --- a/internal/adapters/dataproviders/database/mongo/pegin.go +++ b/internal/adapters/dataproviders/database/mongo/pegin.go @@ -51,6 +51,10 @@ func (repo *peginMongoRepository) GetQuote(ctx context.Context, hash string) (*q dbCtx, cancel := context.WithTimeout(ctx, dbTimeout) defer cancel() + if err := quote.ValidateQuoteHash(hash); err != nil { + return nil, err + } + collection := repo.conn.Collection(PeginQuoteCollection) filter := bson.D{primitive.E{Key: "hash", Value: hash}} @@ -69,6 +73,10 @@ func (repo *peginMongoRepository) GetRetainedQuote(ctx context.Context, hash str dbCtx, cancel := context.WithTimeout(ctx, dbTimeout) defer cancel() + if err := quote.ValidateQuoteHash(hash); err != nil { + return nil, err + } + collection := repo.conn.Collection(RetainedPeginQuoteCollection) filter := bson.D{primitive.E{Key: "quote_hash", Value: hash}} diff --git a/internal/adapters/dataproviders/database/mongo/pegin_test.go b/internal/adapters/dataproviders/database/mongo/pegin_test.go index 043fb1a7..c4625972 100644 --- a/internal/adapters/dataproviders/database/mongo/pegin_test.go +++ b/internal/adapters/dataproviders/database/mongo/pegin_test.go @@ -84,13 +84,13 @@ func TestPeginMongoRepository_GetQuote(t *testing.T) { t.Run("Get pegin quote successfully", func(t *testing.T) { const expectedLog = "READ interaction with db: {FedBtcAddress:3LxPz39femVBL278mTiBvgzBNMVFqXssoH LbcAddress:0xAA9cAf1e3967600578727F975F283446A3Da6612 LpRskAddress:0x4202bac9919c3412fc7c8be4e678e26279386603 BtcRefundAddress:171gGjg8NeLUonNSrFmgwkgT1jgqzXR6QX RskRefundAddress:0xaD0DE1962ab903E06C725A1b343b7E8950a0Ff82 LpBtcAddress:17kksixYkbHeLy9okV16kr4eAxVhFkRhP CallFee:100000000000000 PenaltyFee:10000000000000 ContractAddress:0xaD0DE1962ab903E06C725A1b343b7E8950a0Ff82 Data:010203 GasLimit:21000 Nonce:8373381263192041574 Value:8000000000000000 AgreementTimestamp:1727298699 TimeForDeposit:3600 LpCallTime:7200 Confirmations:2 CallOnRegister:true GasFee:1341211956000 ProductFeeAmount:1}" repo := mongo.NewPeginMongoRepository(mongo.NewConnection(client)) - collection.On("FindOne", mock.Anything, bson.D{primitive.E{Key: "hash", Value: test.AnyString}}). + collection.On("FindOne", mock.Anything, bson.D{primitive.E{Key: "hash", Value: test.AnyHash}}). Return(mongoDb.NewSingleResultFromDocument(mongo.StoredPeginQuote{ PeginQuote: testPeginQuote, Hash: test.AnyString, }, nil, nil)).Once() defer assertDbInteractionLog(t, expectedLog)() - result, err := repo.GetQuote(context.Background(), test.AnyString) + result, err := repo.GetQuote(context.Background(), test.AnyHash) collection.AssertExpectations(t) require.NoError(t, err) assert.Equal(t, testPeginQuote, *result) @@ -99,7 +99,7 @@ func TestPeginMongoRepository_GetQuote(t *testing.T) { repo := mongo.NewPeginMongoRepository(mongo.NewConnection(client)) collection.On("FindOne", mock.Anything, mock.Anything). Return(mongoDb.NewSingleResultFromDocument(mongo.StoredPeginQuote{}, assert.AnError, nil)).Once() - result, err := repo.GetQuote(context.Background(), test.AnyString) + result, err := repo.GetQuote(context.Background(), test.AnyHash) collection.AssertExpectations(t) require.Error(t, err) assert.Nil(t, result) @@ -108,11 +108,18 @@ func TestPeginMongoRepository_GetQuote(t *testing.T) { repo := mongo.NewPeginMongoRepository(mongo.NewConnection(client)) collection.On("FindOne", mock.Anything, mock.Anything). Return(mongoDb.NewSingleResultFromDocument(mongo.StoredPeginQuote{}, mongoDb.ErrNoDocuments, nil)).Once() - result, err := repo.GetQuote(context.Background(), test.AnyString) + result, err := repo.GetQuote(context.Background(), test.AnyHash) collection.AssertExpectations(t) require.NoError(t, err) assert.Nil(t, result) }) + t.Run("Fail on invalid pegin quote hash", func(t *testing.T) { + repo := mongo.NewPeginMongoRepository(mongo.NewConnection(client)) + result, err := repo.GetQuote(context.Background(), test.AnyString) + collection.AssertNotCalled(t, "FindOne") + require.Error(t, err) + assert.Nil(t, result) + }) } func TestPeginMongoRepository_GetRetainedQuote(t *testing.T) { @@ -121,10 +128,10 @@ func TestPeginMongoRepository_GetRetainedQuote(t *testing.T) { t.Run("Get retained pegin quote successfully", func(t *testing.T) { const expectedLog = "READ interaction with db: {QuoteHash:8d1ba2cb559a6ebe41f19131602467e1d939682d651b2a91e55b86bc664a6819 DepositAddress:2N7Vw5f59V3o3bDcaJK5oA829LFTBYZHLoG Signature:b24831aac7230910087d9818b378a31679be5e3991a7227cc160bc3add09e1645a26e9c740e3467f53953d7ec086c82bf8ef0eb03c118d0382ee6049a8f0119f1c RequiredLiquidity:100 State:CallForUserSucceeded UserBtcTxHash:619c4d69ccaa5f78aaa2284817cf070609ac40af3792916ca3d0ef82b14af75f CallForUserTxHash:0x2c73de184c80797c04a655217d121588e8d5c228d3e0cc26187cb249123aa7c3 RegisterPeginTxHash:0x3a0feaef4d803468ba5bfc1db78f4d2568de1b7cf002dec5991c469e6719db89}" repo := mongo.NewPeginMongoRepository(mongo.NewConnection(client)) - collection.On("FindOne", mock.Anything, bson.D{primitive.E{Key: "quote_hash", Value: test.AnyString}}). + collection.On("FindOne", mock.Anything, bson.D{primitive.E{Key: "quote_hash", Value: test.AnyHash}}). Return(mongoDb.NewSingleResultFromDocument(testRetainedPeginQuote, nil, nil)).Once() defer assertDbInteractionLog(t, expectedLog)() - result, err := repo.GetRetainedQuote(context.Background(), test.AnyString) + result, err := repo.GetRetainedQuote(context.Background(), test.AnyHash) collection.AssertExpectations(t) require.NoError(t, err) assert.Equal(t, testRetainedPeginQuote, *result) @@ -133,7 +140,7 @@ func TestPeginMongoRepository_GetRetainedQuote(t *testing.T) { repo := mongo.NewPeginMongoRepository(mongo.NewConnection(client)) collection.On("FindOne", mock.Anything, mock.Anything). Return(mongoDb.NewSingleResultFromDocument(quote.RetainedPeginQuote{}, assert.AnError, nil)).Once() - result, err := repo.GetRetainedQuote(context.Background(), test.AnyString) + result, err := repo.GetRetainedQuote(context.Background(), test.AnyHash) collection.AssertExpectations(t) require.Error(t, err) assert.Nil(t, result) @@ -142,11 +149,18 @@ func TestPeginMongoRepository_GetRetainedQuote(t *testing.T) { repo := mongo.NewPeginMongoRepository(mongo.NewConnection(client)) collection.On("FindOne", mock.Anything, mock.Anything). Return(mongoDb.NewSingleResultFromDocument(quote.RetainedPeginQuote{}, mongoDb.ErrNoDocuments, nil)).Once() - result, err := repo.GetRetainedQuote(context.Background(), test.AnyString) + result, err := repo.GetRetainedQuote(context.Background(), test.AnyHash) collection.AssertExpectations(t) require.NoError(t, err) assert.Nil(t, result) }) + t.Run("Fail on invalid pegin quote hash", func(t *testing.T) { + repo := mongo.NewPeginMongoRepository(mongo.NewConnection(client)) + result, err := repo.GetRetainedQuote(context.Background(), test.AnyString) + collection.AssertNotCalled(t, "FindOne") + require.Error(t, err) + assert.Nil(t, result) + }) } func TestPeginMongoRepository_InsertRetainedQuote(t *testing.T) { diff --git a/internal/adapters/dataproviders/database/mongo/pegout.go b/internal/adapters/dataproviders/database/mongo/pegout.go index 38058409..79599929 100644 --- a/internal/adapters/dataproviders/database/mongo/pegout.go +++ b/internal/adapters/dataproviders/database/mongo/pegout.go @@ -10,6 +10,7 @@ import ( "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" + "regexp" ) const ( @@ -53,6 +54,10 @@ func (repo *pegoutMongoRepository) GetQuote(ctx context.Context, hash string) (* dbCtx, cancel := context.WithTimeout(ctx, dbTimeout) defer cancel() + if err := quote.ValidateQuoteHash(hash); err != nil { + return nil, err + } + collection := repo.conn.Collection(PegoutQuoteCollection) filter := bson.D{primitive.E{Key: "hash", Value: hash}} @@ -71,6 +76,10 @@ func (repo *pegoutMongoRepository) GetRetainedQuote(ctx context.Context, hash st dbCtx, cancel := context.WithTimeout(ctx, dbTimeout) defer cancel() + if err := quote.ValidateQuoteHash(hash); err != nil { + return nil, err + } + collection := repo.conn.Collection(RetainedPegoutQuoteCollection) filter := bson.D{primitive.E{Key: "quote_hash", Value: hash}} @@ -101,7 +110,8 @@ func (repo *pegoutMongoRepository) ListPegoutDepositsByAddress(ctx context.Conte dbCtx, cancel := context.WithTimeout(ctx, dbTimeout) defer cancel() - filter := bson.M{"from": bson.M{"$regex": address, "$options": "i"}} + sanitizedAddress := regexp.QuoteMeta(address) + filter := bson.M{"from": bson.M{"$regex": sanitizedAddress, "$options": "i"}} sort := options.Find().SetSort(bson.M{"timestamp": -1}) cursor, err := repo.conn.Collection(DepositEventsCollection).Find(dbCtx, filter, sort) if err != nil { diff --git a/internal/adapters/dataproviders/database/mongo/pegout_test.go b/internal/adapters/dataproviders/database/mongo/pegout_test.go index f3d6d0a6..9cc4714a 100644 --- a/internal/adapters/dataproviders/database/mongo/pegout_test.go +++ b/internal/adapters/dataproviders/database/mongo/pegout_test.go @@ -96,13 +96,13 @@ func TestPegoutMongoRepository_GetQuote(t *testing.T) { t.Run("Get pegout quote successfully", func(t *testing.T) { const expectedLog = "READ interaction with db: {LbcAddress:0xc2A630c053D12D63d32b025082f6Ba268db18300 LpRskAddress:0x7c4890a0f1d4bbf2c669ac2d1effa185c505359b BtcRefundAddress:n2Ge4xMVQKp5Hzzf8xTBJBLppRgjRZYYyq RskRefundAddress:0x79568C2989232dcA1840087d73d403602364c0D4 LpBtcAddress:mvL2bVzGUeC9oqVyQWJ4PxQspFzKgjzAqe CallFee:100000000000000 PenaltyFee:10000000000000 Nonce:6410832321595034747 DepositAddress:n2Ge4xMVQKp5Hzzf8xTBJBLppRgjRZYYyq Value:5000000000000000 AgreementTimestamp:1721944367 DepositDateLimit:1721951567 DepositConfirmations:4 TransferConfirmations:2 TransferTime:7200 ExpireDate:1721958767 ExpireBlock:5366409 GasFee:4170000000000 ProductFeeAmount:13}" repo := mongo.NewPegoutMongoRepository(mongo.NewConnection(client)) - collection.On("FindOne", mock.Anything, bson.D{primitive.E{Key: "hash", Value: test.AnyString}}). + collection.On("FindOne", mock.Anything, bson.D{primitive.E{Key: "hash", Value: test.AnyHash}}). Return(mongoDb.NewSingleResultFromDocument(mongo.StoredPegoutQuote{ PegoutQuote: testPegoutQuote, Hash: test.AnyString, }, nil, nil)).Once() defer assertDbInteractionLog(t, expectedLog)() - result, err := repo.GetQuote(context.Background(), test.AnyString) + result, err := repo.GetQuote(context.Background(), test.AnyHash) collection.AssertExpectations(t) require.NoError(t, err) assert.Equal(t, testPegoutQuote, *result) @@ -111,7 +111,7 @@ func TestPegoutMongoRepository_GetQuote(t *testing.T) { repo := mongo.NewPegoutMongoRepository(mongo.NewConnection(client)) collection.On("FindOne", mock.Anything, mock.Anything). Return(mongoDb.NewSingleResultFromDocument(mongo.StoredPegoutQuote{}, assert.AnError, nil)).Once() - result, err := repo.GetQuote(context.Background(), test.AnyString) + result, err := repo.GetQuote(context.Background(), test.AnyHash) collection.AssertExpectations(t) require.Error(t, err) assert.Nil(t, result) @@ -120,11 +120,18 @@ func TestPegoutMongoRepository_GetQuote(t *testing.T) { repo := mongo.NewPegoutMongoRepository(mongo.NewConnection(client)) collection.On("FindOne", mock.Anything, mock.Anything). Return(mongoDb.NewSingleResultFromDocument(mongo.StoredPegoutQuote{}, mongoDb.ErrNoDocuments, nil)).Once() - result, err := repo.GetQuote(context.Background(), test.AnyString) + result, err := repo.GetQuote(context.Background(), test.AnyHash) collection.AssertExpectations(t) require.NoError(t, err) assert.Nil(t, result) }) + t.Run("Fail on invalid pegout quote hash", func(t *testing.T) { + repo := mongo.NewPegoutMongoRepository(mongo.NewConnection(client)) + result, err := repo.GetQuote(context.Background(), test.AnyString) + collection.AssertNotCalled(t, "FindOne") + require.Error(t, err) + assert.Nil(t, result) + }) } func TestPegoutMongoRepository_GetRetainedQuote(t *testing.T) { @@ -133,10 +140,10 @@ func TestPegoutMongoRepository_GetRetainedQuote(t *testing.T) { t.Run("Get retained pegout quote successfully", func(t *testing.T) { const expectedLog = "READ interaction with db: {QuoteHash:27d70ec2bc2c3154dc9a5b53b118a755441b22bc1c8ccde967ed33609970c25f DepositAddress:mkE1WWdiu5VgjfugomDk8GxV6JdEEEJR9s Signature:5c9eab91c753355f87c19d09ea88b2fd02773981e513bc2821fed5ceba0d452a0a3d21e2252cb35348ce5c6803117e3abb62837beb8f5866a375ce66587d004b1c RequiredLiquidity:55 State:WaitingForDepositConfirmations UserRskTxHash:0x6b2e1e4daf8cf00c5c3534b72cdeec3526e8a38f70c11e44888b6e4ae1ee7d38 LpBtcTxHash:6ac3779dc33ad52f3409cbb909bcd458745995496a2a3954406206f6e5d4cb0e RefundPegoutTxHash:0x8e773a2826e73f8e5792304379a7e46dff38f17089c6d344335e03537b31c2bc BridgeRefundTxHash:0x4f3f6f0664a732e4c907971e75c1e3fd8671461dcb53f566660432fc47255d8b}" repo := mongo.NewPegoutMongoRepository(mongo.NewConnection(client)) - collection.On("FindOne", mock.Anything, bson.D{primitive.E{Key: "quote_hash", Value: test.AnyString}}). + collection.On("FindOne", mock.Anything, bson.D{primitive.E{Key: "quote_hash", Value: test.AnyHash}}). Return(mongoDb.NewSingleResultFromDocument(testRetainedPegoutQuote, nil, nil)).Once() defer assertDbInteractionLog(t, expectedLog)() - result, err := repo.GetRetainedQuote(context.Background(), test.AnyString) + result, err := repo.GetRetainedQuote(context.Background(), test.AnyHash) collection.AssertExpectations(t) require.NoError(t, err) assert.Equal(t, testRetainedPegoutQuote, *result) @@ -145,7 +152,7 @@ func TestPegoutMongoRepository_GetRetainedQuote(t *testing.T) { repo := mongo.NewPegoutMongoRepository(mongo.NewConnection(client)) collection.On("FindOne", mock.Anything, mock.Anything). Return(mongoDb.NewSingleResultFromDocument(quote.RetainedPegoutQuote{}, assert.AnError, nil)).Once() - result, err := repo.GetRetainedQuote(context.Background(), test.AnyString) + result, err := repo.GetRetainedQuote(context.Background(), test.AnyHash) collection.AssertExpectations(t) require.Error(t, err) assert.Nil(t, result) @@ -154,11 +161,18 @@ func TestPegoutMongoRepository_GetRetainedQuote(t *testing.T) { repo := mongo.NewPegoutMongoRepository(mongo.NewConnection(client)) collection.On("FindOne", mock.Anything, mock.Anything). Return(mongoDb.NewSingleResultFromDocument(quote.RetainedPegoutQuote{}, mongoDb.ErrNoDocuments, nil)).Once() - result, err := repo.GetRetainedQuote(context.Background(), test.AnyString) + result, err := repo.GetRetainedQuote(context.Background(), test.AnyHash) collection.AssertExpectations(t) require.NoError(t, err) assert.Nil(t, result) }) + t.Run("Fail on invalid pegout quote hash", func(t *testing.T) { + repo := mongo.NewPegoutMongoRepository(mongo.NewConnection(client)) + result, err := repo.GetRetainedQuote(context.Background(), test.AnyString) + collection.AssertNotCalled(t, "FindOne") + require.Error(t, err) + assert.Nil(t, result) + }) } func TestPegoutMongoRepository_InsertRetainedQuote(t *testing.T) { @@ -360,6 +374,17 @@ func TestPegoutMongoRepository_ListPegoutDepositsByAddress(t *testing.T) { require.Error(t, err) assert.Empty(t, result) }) + t.Run("Should sanitize address properly", func(t *testing.T) { + repo := mongo.NewPegoutMongoRepository(mongo.NewConnection(client)) + collection.On("Find", mock.Anything, + bson.M{"from": bson.M{"$regex": "0x1234567890abcdef1234567890abcdef12345678\\(a\\+\\)\\+", "$options": "i"}}, + options.Find().SetSort(bson.M{"timestamp": -1}), + ).Return(mongoDb.NewCursorFromDocuments([]any{testPegoutDeposit}, nil, nil)).Once() + result, err := repo.ListPegoutDepositsByAddress(context.Background(), "0x1234567890abcdef1234567890abcdef12345678(a+)+") + collection.AssertExpectations(t) + require.NoError(t, err) + assert.Equal(t, []quote.PegoutDeposit{testPegoutDeposit}[0], result[0]) + }) } func TestPegoutMongoRepository_UpsertPegoutDeposit(t *testing.T) { diff --git a/internal/adapters/entrypoints/rest/server/cookies/store_test.go b/internal/adapters/entrypoints/rest/server/cookies/store_test.go index 14768479..06a58121 100644 --- a/internal/adapters/entrypoints/rest/server/cookies/store_test.go +++ b/internal/adapters/entrypoints/rest/server/cookies/store_test.go @@ -30,9 +30,9 @@ func TestUniqueSessionStore_New(t *testing.T) { store := cookies.NewUniqueSessionStore(cookieName, k1, k2) t.Run("should return an error if the session name is different", func(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/", nil) - session, err := store.New(req, test.AnyHash) + session, err := store.New(req, test.AnyString) assertDummySession(t, session) - require.ErrorContains(t, err, "UniqueSessionStore is expecting cookie session name and received any hash") + require.ErrorContains(t, err, "UniqueSessionStore is expecting cookie session name and received any value") }) t.Run("should return an new session the first time", func(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/", nil) diff --git a/internal/adapters/entrypoints/watcher/pegin_btc_deposit_watcher_test.go b/internal/adapters/entrypoints/watcher/pegin_btc_deposit_watcher_test.go index c8763bed..a6a532af 100644 --- a/internal/adapters/entrypoints/watcher/pegin_btc_deposit_watcher_test.go +++ b/internal/adapters/entrypoints/watcher/pegin_btc_deposit_watcher_test.go @@ -227,7 +227,7 @@ func TestPeginDepositAddressWatcher_Start_BlockchainCheck(t *testing.T) { expiredQuote := quote.PeginQuote{Nonce: 6, AgreementTimestamp: 1} t.Run("should handle error when expiring quotes", func(t *testing.T) { resetMocks() - checkFunction := test.AssertLogContains(t, "Error updating expired quote (any hash)") + checkFunction := test.AssertLogContains(t, "Error updating expired quote (d8f5d705f146230553a8aec9a290a19bf4311187fa0489d41207d7215b0b65cb)") peginRepository.EXPECT().UpdateRetainedQuote(mock.Anything, mock.Anything).Return(assert.AnError).Once() btcRpc.On("GetHeight").Return(big.NewInt(9), nil).Once() btcWallet.On("ImportAddress", test.AnyAddress).Return(nil).Once() diff --git a/test/utils.go b/test/utils.go index 51741c61..291cbd5c 100644 --- a/test/utils.go +++ b/test/utils.go @@ -30,7 +30,7 @@ const ( AnyRskAddress = "0x79568c2989232dCa1840087D73d403602364c0D4" AnyBtcAddress = "mvL2bVzGUeC9oqVyQWJ4PxQspFzKgjzAqe" AnyString = "any value" - AnyHash = "any hash" + AnyHash = "d8f5d705f146230553a8aec9a290a19bf4311187fa0489d41207d7215b0b65cb" AnyUrl = "url.com" keyPath = "../../docker-compose/localstack/local-key.json" KeyPassword = "test"