Skip to content

Commit

Permalink
Merge branch 'develop' into combined-bot-prs-branch
Browse files Browse the repository at this point in the history
  • Loading branch information
shahzadlone committed Jul 5, 2024
2 parents 38148ce + 24fa14f commit 397801d
Show file tree
Hide file tree
Showing 62 changed files with 1,984 additions and 227 deletions.
18 changes: 17 additions & 1 deletion cli/collection_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,25 @@ import (
"github.com/spf13/cobra"

"github.com/sourcenetwork/defradb/client"
"github.com/sourcenetwork/defradb/internal/db"
)

func MakeCollectionCreateCommand() *cobra.Command {
var file string
var shouldEncrypt bool
var cmd = &cobra.Command{
Use: "create [-i --identity] <document>",
Use: "create [-i --identity] [-e --encrypt] <document>",
Short: "Create a new document.",
Long: `Create a new document.
Options:
-i, --identity
Marks the document as private and set the identity as the owner. The access to the document
and permissions are controlled by ACP (Access Control Policy).
-e, --encrypt
Encrypt flag specified if the document needs to be encrypted. If set, DefraDB will generate a
symmetric key for encryption using AES-GCM.
Example: create from string:
defradb client collection create --name User '{ "name": "Bob" }'
Expand Down Expand Up @@ -69,6 +80,9 @@ Example: create from stdin:
return cmd.Usage()
}

txn, _ := db.TryGetContextTxn(cmd.Context())
setContextDocEncryption(cmd, shouldEncrypt, txn)

if client.IsJSONArray(docData) {
docs, err := client.NewDocsFromJSON(docData, col.Definition())
if err != nil {
Expand All @@ -84,6 +98,8 @@ Example: create from stdin:
return col.Create(cmd.Context(), doc)
},
}
cmd.PersistentFlags().BoolVarP(&shouldEncrypt, "encrypt", "e", false,
"Flag to enable encryption of the document")
cmd.Flags().StringVarP(&file, "file", "f", "", "File containing document(s)")
return cmd
}
15 changes: 15 additions & 0 deletions cli/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@ import (

acpIdentity "github.com/sourcenetwork/defradb/acp/identity"
"github.com/sourcenetwork/defradb/client"
"github.com/sourcenetwork/defradb/datastore"
"github.com/sourcenetwork/defradb/http"
"github.com/sourcenetwork/defradb/internal/db"
"github.com/sourcenetwork/defradb/internal/encryption"
"github.com/sourcenetwork/defradb/keyring"
)

Expand Down Expand Up @@ -160,6 +162,19 @@ func setContextIdentity(cmd *cobra.Command, privateKeyHex string) error {
return nil
}

// setContextDocEncryption sets doc encryption for the current command context.
func setContextDocEncryption(cmd *cobra.Command, shouldEncrypt bool, txn datastore.Txn) {
if !shouldEncrypt {
return
}
ctx := cmd.Context()
if txn != nil {
ctx = encryption.ContextWithStore(ctx, txn)
}
ctx = encryption.SetContextConfig(ctx, encryption.DocEncConfig{IsEncrypted: true})
cmd.SetContext(ctx)
}

// setContextRootDir sets the rootdir for the current command context.
func setContextRootDir(cmd *cobra.Command) error {
rootdir, err := cmd.Root().PersistentFlags().GetString("rootdir")
Expand Down
2 changes: 1 addition & 1 deletion client/document.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ func NewDocFromMap(data map[string]any, collectionDefinition CollectionDefinitio
return doc, nil
}

var jsonArrayPattern = regexp.MustCompile(`^\s*\[.*\]\s*$`)
var jsonArrayPattern = regexp.MustCompile(`(?s)^\s*\[.*\]\s*$`)

// IsJSONArray returns true if the given byte array is a JSON Array.
func IsJSONArray(obj []byte) bool {
Expand Down
63 changes: 63 additions & 0 deletions client/document_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,3 +206,66 @@ func TestNewFromJSON_WithInvalidJSONFieldValueSimpleString_Error(t *testing.T) {
_, err := NewDocFromJSON(objWithJSONField, def)
require.ErrorContains(t, err, "invalid JSON payload. Payload: blah")
}

func TestIsJSONArray(t *testing.T) {
tests := []struct {
name string
input []byte
expected bool
}{
{
name: "Valid JSON Array",
input: []byte(`[{"name":"John","age":21},{"name":"Islam","age":33}]`),
expected: true,
},
{
name: "Valid Empty JSON Array",
input: []byte(`[]`),
expected: true,
},
{
name: "Valid JSON Object",
input: []byte(`{"name":"John","age":21}`),
expected: false,
},
{
name: "Invalid JSON String",
input: []byte(`{"name":"John","age":21`),
expected: false,
},
{
name: "Non-JSON String",
input: []byte(`Hello, World!`),
expected: false,
},
{
name: "Array of Primitives",
input: []byte(`[1, 2, 3, 4]`),
expected: true,
},
{
name: "Nested JSON Array",
input: []byte(`[[1, 2], [3, 4]]`),
expected: true,
},
{
name: "Valid JSON Array with Whitespace",
input: []byte(`
[
{ "name": "John", "age": 21 },
{ "name": "Islam", "age": 33 }
]
`),
expected: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
actual := IsJSONArray(tt.input)
if actual != tt.expected {
t.Errorf("IsJSONArray(%s) = %v; expected %v", tt.input, actual, tt.expected)
}
})
}
}
3 changes: 3 additions & 0 deletions client/request/consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,13 @@ const (

Cid = "cid"
Input = "input"
Inputs = "inputs"
FieldName = "field"
FieldIDName = "fieldId"
ShowDeleted = "showDeleted"

EncryptArgName = "encrypt"

FilterClause = "filter"
GroupByClause = "groupBy"
LimitClause = "limit"
Expand Down
9 changes: 9 additions & 0 deletions client/request/mutation.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,15 @@ type ObjectMutation struct {
//
// This is ignored for [DeleteObjects] mutations.
Input map[string]any

// Inputs is the array of json representations of the fieldName-value pairs of document
// properties to mutate.
//
// This is ignored for [DeleteObjects] mutations.
Inputs []map[string]any

// Encrypt is a boolean flag that indicates whether the input data should be encrypted.
Encrypt bool
}

// ToSelect returns a basic Select object, with the same Name, Alias, and Fields as
Expand Down
47 changes: 47 additions & 0 deletions datastore/mocks/txn.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions datastore/multi.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@ var (
headStoreKey = rootStoreKey.ChildString("heads")
blockStoreKey = rootStoreKey.ChildString("blocks")
peerStoreKey = rootStoreKey.ChildString("ps")
encStoreKey = rootStoreKey.ChildString("enc")
)

type multistore struct {
root DSReaderWriter
data DSReaderWriter
enc DSReaderWriter
head DSReaderWriter
peer DSBatching
system DSReaderWriter
Expand All @@ -43,6 +45,7 @@ func MultiStoreFrom(rootstore ds.Datastore) MultiStore {
ms := &multistore{
root: rootRW,
data: prefix(rootRW, dataStoreKey),
enc: prefix(rootRW, encStoreKey),
head: prefix(rootRW, headStoreKey),
peer: namespace.Wrap(rootstore, peerStoreKey),
system: prefix(rootRW, systemStoreKey),
Expand All @@ -57,6 +60,11 @@ func (ms multistore) Datastore() DSReaderWriter {
return ms.data
}

// Encstore implements MultiStore.
func (ms multistore) Encstore() DSReaderWriter {
return ms.enc
}

// Headstore implements MultiStore.
func (ms multistore) Headstore() DSReaderWriter {
return ms.head
Expand Down
20 changes: 10 additions & 10 deletions datastore/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,26 +34,26 @@ type Rootstore interface {
type MultiStore interface {
Rootstore() DSReaderWriter

// Datastore is a wrapped root DSReaderWriter
// under the /data namespace
// Datastore is a wrapped root DSReaderWriter under the /data namespace
Datastore() DSReaderWriter

// Headstore is a wrapped root DSReaderWriter
// under the /head namespace
// Encstore is a wrapped root DSReaderWriter under the /enc namespace
// This store is used for storing symmetric encryption keys for doc encryption.
// The store keys are comprised of docID + field name.
Encstore() DSReaderWriter

// Headstore is a wrapped root DSReaderWriter under the /head namespace
Headstore() DSReaderWriter

// Peerstore is a wrapped root DSReaderWriter
// as a ds.Batching, embedded into a DSBatching
// Peerstore is a wrapped root DSReaderWriter as a ds.Batching, embedded into a DSBatching
// under the /peers namespace
Peerstore() DSBatching

// Blockstore is a wrapped root DSReaderWriter
// as a Blockstore, embedded into a Blockstore
// Blockstore is a wrapped root DSReaderWriter as a Blockstore, embedded into a Blockstore
// under the /blocks namespace
Blockstore() Blockstore

// Headstore is a wrapped root DSReaderWriter
// under the /system namespace
// Headstore is a wrapped root DSReaderWriter under the /system namespace
Systemstore() DSReaderWriter
}

Expand Down
12 changes: 11 additions & 1 deletion docs/website/references/cli/defradb_client_collection_create.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ Create a new document.
### Synopsis

Create a new document.

Options:
-i, --identity
Marks the document as private and set the identity as the owner. The access to the document
and permissions are controlled by ACP (Access Control Policy).

-e, --encrypt
Encrypt flag specified if the document needs to be encrypted. If set, DefraDB will generate a
symmetric key for encryption using AES-GCM.

Example: create from string:
defradb client collection create --name User '{ "name": "Bob" }'
Expand All @@ -24,12 +33,13 @@ Example: create from stdin:


```
defradb client collection create [-i --identity] <document> [flags]
defradb client collection create [-i --identity] [-e --encrypt] <document> [flags]
```

### Options

```
-e, --encrypt Flag to enable encryption of the document
-f, --file string File containing document(s)
-h, --help help for create
```
Expand Down
4 changes: 4 additions & 0 deletions http/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -349,12 +349,16 @@ func (c *Client) ExecRequest(
result.GQL.Errors = []error{err}
return result
}

req, err := http.NewRequestWithContext(ctx, http.MethodPost, methodURL.String(), bytes.NewBuffer(body))
if err != nil {
result.GQL.Errors = []error{err}
return result
}
err = c.http.setDefaultHeaders(req)

setDocEncryptionFlagIfNeeded(ctx, req)

if err != nil {
result.GQL.Errors = []error{err}
return result
Expand Down
14 changes: 14 additions & 0 deletions http/client_collection.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
sse "github.com/vito/go-sse/sse"

"github.com/sourcenetwork/defradb/client"
"github.com/sourcenetwork/defradb/internal/encryption"
)

var _ client.Collection = (*Collection)(nil)
Expand Down Expand Up @@ -78,6 +79,8 @@ func (c *Collection) Create(
return err
}

setDocEncryptionFlagIfNeeded(ctx, req)

_, err = c.http.request(req)
if err != nil {
return err
Expand Down Expand Up @@ -114,6 +117,8 @@ func (c *Collection) CreateMany(
return err
}

setDocEncryptionFlagIfNeeded(ctx, req)

_, err = c.http.request(req)
if err != nil {
return err
Expand All @@ -125,6 +130,15 @@ func (c *Collection) CreateMany(
return nil
}

func setDocEncryptionFlagIfNeeded(ctx context.Context, req *http.Request) {
encConf := encryption.GetContextConfig(ctx)
if encConf.HasValue() && encConf.Value().IsEncrypted {
q := req.URL.Query()
q.Set(docEncryptParam, "true")
req.URL.RawQuery = q.Encode()
}
}

func (c *Collection) Update(
ctx context.Context,
doc *client.Document,
Expand Down
Loading

0 comments on commit 397801d

Please sign in to comment.