Skip to content

Commit

Permalink
[FAB-10688] CouchDB key cannot begin with underscore
Browse files Browse the repository at this point in the history
The key for CouchDB documents cannot begin with an underscore.

This change adds validation in simulation and commit to prevent the
key from beginning with an underscore.

Remove the ~metadata restriction.

Documentation is updated to reflect the change.

Change-Id: I7a0bb9e0af9e513a982806693e64d18fae226d2d
Signed-off-by: Chris Elder <chris.elder@us.ibm.com>
  • Loading branch information
Chris Elder committed Jun 20, 2018
1 parent 3436f45 commit 186ad00
Show file tree
Hide file tree
Showing 4 changed files with 25 additions and 11 deletions.
14 changes: 12 additions & 2 deletions core/ledger/kvledger/txmgmt/statedb/statecouchdb/couchdoc_conv.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"fmt"
"strconv"
"strings"
"unicode/utf8"

"github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/statedb"
"github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/version"
Expand All @@ -23,7 +24,6 @@ const (
revField = "_rev"
versionField = "~version"
deletedField = "_deleted"
metadataField = "~metadata"
)

type keyValue struct {
Expand All @@ -47,7 +47,7 @@ func castToJSON(b []byte) (jsonValue, error) {

func (v jsonValue) checkReservedFieldsNotPresent() error {
for fieldName := range v {
if fieldName == versionField || fieldName == metadataField || strings.HasPrefix(fieldName, "_") {
if fieldName == versionField || strings.HasPrefix(fieldName, "_") {
return fmt.Errorf("The field [%s] is not valid for the CouchDB state database", fieldName)
}
}
Expand Down Expand Up @@ -202,6 +202,16 @@ func validateValue(value []byte) error {
return jsonVal.checkReservedFieldsNotPresent()
}

func validateKey(key string) error {
if !utf8.ValidString(key) {
return fmt.Errorf("Key should be a valid utf8 string: [%x]", key)
}
if strings.HasPrefix(key, "_") {
return fmt.Errorf("The key [%s] is not valid for the CouchDB state database. The key must not begin with \"_\"", key)
}
return nil
}

// removeJSONRevision removes the "_rev" if this is a JSON
func removeJSONRevision(jsonValue *[]byte) error {
jsonVal, err := castToJSON(*jsonValue)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"encoding/json"
"fmt"
"sync"
"unicode/utf8"

"github.com/hyperledger/fabric/common/flogging"
"github.com/hyperledger/fabric/core/common/ccprovider"
Expand Down Expand Up @@ -200,8 +199,9 @@ func (vdb *VersionedDB) GetCachedVersion(namespace string, key string) (*version

// ValidateKeyValue implements method in VersionedDB interface
func (vdb *VersionedDB) ValidateKeyValue(key string, value []byte) error {
if !utf8.ValidString(key) {
return fmt.Errorf("Key should be a valid utf8 string: [%x]", key)
err := validateKey(key)
if err != nil {
return err
}
return validateValue(value)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,18 +200,18 @@ func TestUtilityFunctions(t *testing.T) {
err = db.ValidateKeyValue(string([]byte{0xff, 0xfe, 0xfd}), []byte("Some random bytes"))
testutil.AssertError(t, err, "ValidateKey should have thrown an error for an invalid utf-8 string")

reservedFields := []string{"~version", "~metadata", "_id", "_test"}
reservedFields := []string{"~version", "_id", "_test"}

// ValidateKey should return an error for a json value that already contains one of the reserved fields
// ValidateKeyValue should return an error for a json value that contains one of the reserved fields
// at the top level
for _, reservedField := range reservedFields {
testVal := fmt.Sprintf(`{"%s":"dummyVal"}`, reservedField)
err = db.ValidateKeyValue("testKey", []byte(testVal))
testutil.AssertError(t, err, fmt.Sprintf(
"ValidateKey should have thrown an error for a json value %s, as contains one of the rserved fields", testVal))
"ValidateKey should have thrown an error for a json value %s, as contains one of the reserved fields", testVal))
}

// ValidateKey should not return an error for a json value that already contains one of the reserved fields
// ValidateKeyValue should not return an error for a json value that contains one of the reserved fields
// if not at the top level
for _, reservedField := range reservedFields {
testVal := fmt.Sprintf(`{"data.%s":"dummyVal"}`, reservedField)
Expand All @@ -220,6 +220,10 @@ func TestUtilityFunctions(t *testing.T) {
"ValidateKey should not have thrown an error the json value %s since the reserved field was not at the top level", testVal))
}

// ValidateKeyValue should return an error for a key that begins with an underscore
err = db.ValidateKeyValue("_testKey", []byte("testValue"))
testutil.AssertError(t, err, "ValidateKey should have thrown an error for a key that begins with an underscore")

}

// TestInvalidJSONFields tests for invalid JSON fields
Expand Down
4 changes: 2 additions & 2 deletions docs/source/couchdb_as_state_database.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ default embedded LevelDB, and move to CouchDB if you require the additional comp
It is a good practice to model chaincode asset data as JSON, so that you have the option to perform
complex rich queries if needed in the future.

.. note:: A JSON document cannot use the following field names at the top level.
These are reserved for internal use.
.. note:: The key for a CouchDB JSON document cannot begin with an underscore ("_"). Also, a JSON
document cannot use the following field names at the top level. These are reserved for internal use.

- ``Any field beginning with an underscore, "_"``
- ``~version``
Expand Down

0 comments on commit 186ad00

Please sign in to comment.