Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Encode keys in URLs with base64url raw #477

Merged
merged 1 commit into from
Jan 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,9 @@ cmd/bdb/bdb

#IDE artifacts
.idea

#Documentation with docusaurus
.docusaurus
node_modules
yarn.lock

1 change: 1 addition & 0 deletions internal/httphandler/data_request_handler.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Copyright IBM Corp. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package httphandler

import (
Expand Down
62 changes: 55 additions & 7 deletions internal/httphandler/data_request_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,19 @@ func TestDataRequestHandler_DataQuery(t *testing.T) {
Key: "foo",
})

sigAbc := testutils.SignatureFromQuery(t, aliceSigner, &types.GetDataQuery{
UserId: submittingUserName,
DbName: dbName,
Key: "abc/def",
})

sigKey1 := testutils.SignatureFromQuery(t, aliceSigner, &types.GetDataQuery{
UserId: submittingUserName,
DbName: dbName,
Key: "key1",
})


testCases := []struct {
name string
requestFactory func() (*http.Request, error)
Expand Down Expand Up @@ -83,6 +96,41 @@ func TestDataRequestHandler_DataQuery(t *testing.T) {
},
expectedStatusCode: http.StatusOK,
},
{
name: "valid get data request - non URL",
expectedResponse: &types.GetDataResponseEnvelope{
Response: &types.GetDataResponse{
Header: &types.ResponseHeader{
NodeId: "testNodeID",
},
Value: []byte("bar"),
Metadata: &types.Metadata{
Version: &types.Version{
TxNum: 1,
BlockNum: 1,
},
},
},
Signature: []byte{0, 0, 0},
},
requestFactory: func() (*http.Request, error) {
req, err := http.NewRequest(http.MethodGet, constants.URLForGetData(dbName, "abc/def"), nil)
if err != nil {
return nil, err
}
req.Header.Set(constants.UserHeader, submittingUserName)
req.Header.Set(constants.SignatureHeader, base64.StdEncoding.EncodeToString(sigAbc))
return req, nil
},
dbMockFactory: func(response *types.GetDataResponseEnvelope) bcdb.DB {
db := &mocks.DB{}
db.On("GetCertificate", submittingUserName).Return(aliceCert, nil)
db.On("GetData", dbName, submittingUserName, "abc/def").Return(response, nil)
db.On("IsDBExists", dbName).Return(true)
return db
},
expectedStatusCode: http.StatusOK,
},
{
name: "submitting user is not eligible to update the key",
requestFactory: func() (*http.Request, error) {
Expand All @@ -102,29 +150,29 @@ func TestDataRequestHandler_DataQuery(t *testing.T) {
return db
},
expectedStatusCode: http.StatusForbidden,
expectedErr: "error while processing 'GET /data/test_database/foo' because access forbidden",
expectedErr: "error while processing 'GET /data/test_database/Zm9v' because access forbidden", // "Zm9v" is base64url of "foo"
},
{
name: "failed to get data",
requestFactory: func() (*http.Request, error) {
req, err := http.NewRequest(http.MethodGet, constants.URLForGetData(dbName, "foo"), nil)
req, err := http.NewRequest(http.MethodGet, constants.URLForGetData(dbName, "key1"), nil)
if err != nil {
return nil, err
}
req.Header.Set(constants.UserHeader, submittingUserName)
req.Header.Set(constants.SignatureHeader, base64.StdEncoding.EncodeToString(sigFoo))
req.Header.Set(constants.SignatureHeader, base64.StdEncoding.EncodeToString(sigKey1))
return req, nil
},
dbMockFactory: func(response *types.GetDataResponseEnvelope) bcdb.DB {
db := &mocks.DB{}
db.On("GetCertificate", submittingUserName).Return(aliceCert, nil)
db.On("IsDBExists", dbName).Return(true)
db.On("GetData", dbName, submittingUserName, "foo").
db.On("GetData", dbName, submittingUserName, "key1").
Return(nil, errors.New("failed to get data"))
return db
},
expectedStatusCode: http.StatusInternalServerError,
expectedErr: "error while processing 'GET /data/test_database/foo' because failed to get data",
expectedErr: "error while processing 'GET /data/test_database/a2V5MQ' because failed to get data", // "a2V5MQ" is base64url of "key1"
},
{
name: "user doesn't exist",
Expand Down Expand Up @@ -488,7 +536,7 @@ func TestDataRequestHandler_DataRangeQuery(t *testing.T) {
return db
},
expectedStatusCode: http.StatusForbidden,
expectedErr: "error while processing 'GET /data/test_database?startkey=\"key1\"&endkey=\"key10\"&limit=10' because access forbidden",
expectedErr: "error while processing 'GET /data/test_database?startkey=a2V5MQ&endkey=a2V5MTA&limit=10' because access forbidden", // "a2V5MQ" and "a2V5MTA" are the base64 url of "key1" and "key10", resp.
},
{
name: "failed to get data",
Expand All @@ -510,7 +558,7 @@ func TestDataRequestHandler_DataRangeQuery(t *testing.T) {
return db
},
expectedStatusCode: http.StatusInternalServerError,
expectedErr: "error while processing 'GET /data/test_database?startkey=\"key1\"&endkey=\"key10\"&limit=10' because failed to get data",
expectedErr: "error while processing 'GET /data/test_database?startkey=a2V5MQ&endkey=a2V5MTA&limit=10' because failed to get data", // "a2V5MQ" and "a2V5MTA" are the base64 url of "key1" and "key10", resp.
},
{
name: "user doesn't exist",
Expand Down
4 changes: 2 additions & 2 deletions internal/httphandler/ledger_request_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -879,7 +879,7 @@ func TestDataProofQuery(t *testing.T) {
return db
},
expectedStatusCode: http.StatusNotFound,
expectedErr: "error while processing 'GET /ledger/proof/data/bdb/key1?block=2' because no proof for block 2, db bdb, key key1, isDeleted false found",
expectedErr: "error while processing 'GET /ledger/proof/data/bdb/a2V5MQ?block=2' because no proof for block 2, db bdb, key key1, isDeleted false found", // "a2V5MQ" is base 64 url of "key1"
},
{
name: "no key exist, deleted is true",
Expand Down Expand Up @@ -907,7 +907,7 @@ func TestDataProofQuery(t *testing.T) {
return db
},
expectedStatusCode: http.StatusNotFound,
expectedErr: "error while processing 'GET /ledger/proof/data/bdb/key1?block=2&deleted=true' because no proof for block 2, db bdb, key key1, isDeleted true found",
expectedErr: "error while processing 'GET /ledger/proof/data/bdb/a2V5MQ?block=2&deleted=true' because no proof for block 2, db bdb, key key1, isDeleted true found", // "a2V5MQ" is base 64 url of "key1"
},
{
name: "wrong url, block param missing",
Expand Down
57 changes: 50 additions & 7 deletions internal/httphandler/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,23 +32,42 @@ func extractVerifiedQueryPayload(w http.ResponseWriter, r *http.Request, queryTy

switch queryType {
case constants.GetData:
key, err := utils.GetBase64urlKey(params, "key")
if err != nil {
utils.SendHTTPResponse(w, http.StatusBadRequest, &types.HttpResponseErr{ErrMsg: err.Error()})
return nil, true
}

payload = &types.GetDataQuery{
UserId: querierUserID,
DbName: params["dbname"],
Key: params["key"],
Key: key,
}

case constants.GetDataRange:
limit, err := strconv.ParseUint(params["limit"], 10, 64)
if err != nil {
utils.SendHTTPResponse(w, http.StatusBadRequest, &types.HttpResponseErr{ErrMsg: err.Error()})
return nil, true
}

startKey, err := utils.GetBase64urlKey(params, "startkey")
if err != nil {
utils.SendHTTPResponse(w, http.StatusBadRequest, &types.HttpResponseErr{ErrMsg: err.Error()})
return nil, true
}

endKey, err := utils.GetBase64urlKey(params, "endkey")
if err != nil {
utils.SendHTTPResponse(w, http.StatusBadRequest, &types.HttpResponseErr{ErrMsg: err.Error()})
return nil, true
}

payload = &types.GetDataRangeQuery{
UserId: querierUserID,
DbName: params["dbname"],
StartKey: params["startkey"][1 : len(params["startkey"])-1],
EndKey: params["endkey"][1 : len(params["endkey"])-1],
StartKey: startKey,
EndKey: endKey,
Limit: limit,
}
case constants.GetUser:
Expand Down Expand Up @@ -144,6 +163,12 @@ func extractVerifiedQueryPayload(w http.ResponseWriter, r *http.Request, queryTy
TxIndex: txIndex,
}
case constants.GetDataProof:
key, err := utils.GetBase64urlKey(params, "key")
if err != nil {
utils.SendHTTPResponse(w, http.StatusBadRequest, err)
return nil, true
}

blockNum, err := utils.GetBlockNum(params)
if err != nil {
utils.SendHTTPResponse(w, http.StatusBadRequest, err)
Expand All @@ -163,7 +188,7 @@ func extractVerifiedQueryPayload(w http.ResponseWriter, r *http.Request, queryTy
UserId: querierUserID,
BlockNumber: blockNum,
DbName: params["dbname"],
Key: params["key"],
Key: key,
IsDeleted: deleted,
}
case constants.GetTxReceipt:
Expand All @@ -172,6 +197,12 @@ func extractVerifiedQueryPayload(w http.ResponseWriter, r *http.Request, queryTy
TxId: params["txId"],
}
case constants.GetHistoricalData:
key, err := utils.GetBase64urlKey(params, "key")
if err != nil {
utils.SendHTTPResponse(w, http.StatusBadRequest, err)
return nil, true
}

version, err := utils.GetVersion(params)
if err != nil {
utils.SendHTTPResponse(w, http.StatusBadRequest, err)
Expand All @@ -191,23 +222,35 @@ func extractVerifiedQueryPayload(w http.ResponseWriter, r *http.Request, queryTy
payload = &types.GetHistoricalDataQuery{
UserId: querierUserID,
DbName: params["dbname"],
Key: params["key"],
Key: key,
Version: version,
Direction: params["direction"],
OnlyDeletes: isOnlyDeletesSet,
MostRecent: isMostRecentSet,
}
case constants.GetDataReaders:
key, err := utils.GetBase64urlKey(params, "key")
if err != nil {
utils.SendHTTPResponse(w, http.StatusBadRequest, err)
return nil, true
}

payload = &types.GetDataReadersQuery{
UserId: querierUserID,
DbName: params["dbname"],
Key: params["key"],
Key: key,
}
case constants.GetDataWriters:
key, err := utils.GetBase64urlKey(params, "key")
if err != nil {
utils.SendHTTPResponse(w, http.StatusBadRequest, err)
return nil, true
}

payload = &types.GetDataWritersQuery{
UserId: querierUserID,
DbName: params["dbname"],
Key: params["key"],
Key: key,
}
case constants.GetDataReadBy:
payload = &types.GetDataReadByQuery{
Expand Down
14 changes: 14 additions & 0 deletions internal/utils/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package utils

import (
"encoding/base64"
"encoding/json"
"fmt"
"log"
Expand Down Expand Up @@ -126,3 +127,16 @@ func GetVersion(params map[string]string) (*types.Version, error) {
TxNum: txNum,
}, nil
}

func GetBase64urlKey(params map[string]string, name string) (string, error) {
base64urlKey, ok := params[name]
if !ok {
return "", &types.HttpResponseErr{ErrMsg: fmt.Sprintf("Missing key: %s (in base64 URL encoding)", name)}
}
keyBytes, err := base64.RawURLEncoding.DecodeString(base64urlKey)
if err != nil {
return "", &types.HttpResponseErr{ErrMsg: fmt.Sprintf("Failed to decode base64 URL key: %s: %s", name, err.Error())}
}

return string(keyBytes), nil
}
Loading