From 4064d1417c63b4c3531a29830112ec799f44abc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferenc=20S=C3=A1rai?= Date: Mon, 19 Feb 2024 01:13:55 +0100 Subject: [PATCH 01/34] feat: add act.go with TODOs feat: Add Act interface feat: Add Marshal, Unmarshal skeleton feat: Refactor AccessType to iota feat: Add upload feat: Rename GenerateAccessControlManifest -> create feat: Add saltLengthIs32 feat: Add Mrshal, Unmarshal impl feat: Add Marshal Unmarshal feat: Remove ManifestEntry json annotations feat: Modify to public finc/method feat: Add ErrSaltLength Add pkg/dynamicaccess Refactor interfaces and implement default structs Refactor typo Refactor History package to use NewHistory() function Add Act interface and default implementation Add ACT use cases to act_ucs.md Add new files and implement interfaces, refactor packeges Update act_ucs.md base usecases Refactor access logic and add mock implementations*** Add DiffieHellman implementation and remove Keystore*** Refactor NewAccessLogic function Replace encryption.go to pkg/encryption Refactor packages Update act_ucs.md Update act_ucs.md Update act_ucs.md Update act_ucs.md Update act_ucs.md --- act_ucs.md | 46 +++++++++++++++++++++++++ pkg/dynamicaccess/accesslogic.go | 41 ++++++++++++++++++++++ pkg/dynamicaccess/accesslogic_test.go | 11 ++++++ pkg/dynamicaccess/act.go | 18 ++++++++++ pkg/dynamicaccess/act_test.go | 19 ++++++++++ pkg/dynamicaccess/container.go | 7 ++++ pkg/dynamicaccess/controller.go | 18 ++++++++++ pkg/dynamicaccess/diffieHellman.go | 31 +++++++++++++++++ pkg/dynamicaccess/diffieHellman_test.go | 10 ++++++ pkg/dynamicaccess/feed.go | 36 +++++++++++++++++++ pkg/dynamicaccess/feed_test.go | 40 +++++++++++++++++++++ pkg/dynamicaccess/grantee.go | 26 ++++++++++++++ pkg/dynamicaccess/grantee_test.go | 24 +++++++++++++ pkg/dynamicaccess/history.go | 22 ++++++++++++ pkg/dynamicaccess/history_test.go | 19 ++++++++++ pkg/dynamicaccess/mock/accesslogic.go | 12 +++++++ pkg/dynamicaccess/mock/container.go | 20 +++++++++++ pkg/dynamicaccess/mock/diffieHellman.go | 12 +++++++ pkg/dynamicaccess/publish.go | 12 +++++++ pkg/dynamicaccess/publish_test.go | 11 ++++++ pkg/dynamicaccess/timestamp.go | 10 ++++++ pkg/dynamicaccess/timestamp_test.go | 1 + 22 files changed, 446 insertions(+) create mode 100644 act_ucs.md create mode 100644 pkg/dynamicaccess/accesslogic.go create mode 100644 pkg/dynamicaccess/accesslogic_test.go create mode 100644 pkg/dynamicaccess/act.go create mode 100644 pkg/dynamicaccess/act_test.go create mode 100644 pkg/dynamicaccess/container.go create mode 100644 pkg/dynamicaccess/controller.go create mode 100644 pkg/dynamicaccess/diffieHellman.go create mode 100644 pkg/dynamicaccess/diffieHellman_test.go create mode 100644 pkg/dynamicaccess/feed.go create mode 100644 pkg/dynamicaccess/feed_test.go create mode 100644 pkg/dynamicaccess/grantee.go create mode 100644 pkg/dynamicaccess/grantee_test.go create mode 100644 pkg/dynamicaccess/history.go create mode 100644 pkg/dynamicaccess/history_test.go create mode 100644 pkg/dynamicaccess/mock/accesslogic.go create mode 100644 pkg/dynamicaccess/mock/container.go create mode 100644 pkg/dynamicaccess/mock/diffieHellman.go create mode 100644 pkg/dynamicaccess/publish.go create mode 100644 pkg/dynamicaccess/publish_test.go create mode 100644 pkg/dynamicaccess/timestamp.go create mode 100644 pkg/dynamicaccess/timestamp_test.go diff --git a/act_ucs.md b/act_ucs.md new file mode 100644 index 00000000000..82067211bfd --- /dev/null +++ b/act_ucs.md @@ -0,0 +1,46 @@ +# ACT user stories + +This file contains the SWARM ACT user stories. + +ZenHub Link: [SpDevTeam](https://app.zenhub.com/workspaces/spdevteam-6544d91246b817002d853e69/board) + +- [ ] **1** +- I'm a publisher +- I upload a content +- I grant access to content +- Viewer try to access to the content +___ + +- [ ] **2** +- I'm a publisher +- I granteed access for viewers to my channel +___ + +- [ ] **2/a** +- I'm a publisher +- I granteed access for additional viewers to my channel +___ + +- [ ] **2/b** +- I'm a publisher +- I remove viewers from the access list to my channel +___ + +- [ ] **3** +- I'm a viewer +- I requested access to the content +- If I got, I can access it +- If I didn't get, I can't +___ + +- [ ] **4** +- I'm a viewer +- I lost access to the content +- I can't access new ones +___ + +- [ ] **5** +- I'm a viewer +- I lost access to the content +- I can not access new ones, but the old ones I can +___ diff --git a/pkg/dynamicaccess/accesslogic.go b/pkg/dynamicaccess/accesslogic.go new file mode 100644 index 00000000000..269fe4c4638 --- /dev/null +++ b/pkg/dynamicaccess/accesslogic.go @@ -0,0 +1,41 @@ +package dynamicaccess + +import ( + "hash" + + "github.com/ethersphere/bee/pkg/dynamicaccess/mock" + encryption "github.com/ethersphere/bee/pkg/encryption" +) + +type AccessLogic interface { + Get(encryped_ref string, publisher string, tag string) (string, error) +} + +type DefaultAccessLogic struct { + diffieHellman DiffieHellman + encryption encryption.Interface + act Act +} + +func (al *DefaultAccessLogic) Get(encryped_ref string, publisher string, tag string) (string, error) { + return "", nil +} + +func NewAccessLogic(key encryption.Key, padding int, initCtr uint32, hashFunc func() hash.Hash) AccessLogic { + return &DefaultAccessLogic{ + diffieHellman: &mock.DiffieHellmanMock{ + SharedSecretFunc: func(publicKey string, tag string, moment []byte) (string, error) { + return publicKey, nil + }, + }, + encryption: encryption.New(key, padding, initCtr, hashFunc), + act: &mock.ContainerMock{ + AddFunc: func(ref string, publisher string, tag string) error { + return nil + }, + GetFunc: func(ref string, publisher string, tag string) (string, error) { + return "", nil + }, + }, + } +} diff --git a/pkg/dynamicaccess/accesslogic_test.go b/pkg/dynamicaccess/accesslogic_test.go new file mode 100644 index 00000000000..4f3ad6c44bc --- /dev/null +++ b/pkg/dynamicaccess/accesslogic_test.go @@ -0,0 +1,11 @@ +package dynamicaccess + +import "testing" + +func TestXxx(t *testing.T) { + //key encryption.Key, padding int, initCtr uint32, hashFunc func() hash.Hash + al := NewAccessLogic(nil, 0, 0, nil) + if al == nil { + t.Errorf("Error creating access logic") + } +} diff --git a/pkg/dynamicaccess/act.go b/pkg/dynamicaccess/act.go new file mode 100644 index 00000000000..b3448260312 --- /dev/null +++ b/pkg/dynamicaccess/act.go @@ -0,0 +1,18 @@ +package dynamicaccess + +type Act interface{} + +type defaultAct struct { +} + +func (a *defaultAct) Add(oldItemKey string, oldRootHash string) (newRootHash string, err error) { + return "", nil +} + +func (a *defaultAct) Get(rootKey string) (value string, err error) { + return "", nil +} + +func NewAct() Container { + return &defaultAct{} +} diff --git a/pkg/dynamicaccess/act_test.go b/pkg/dynamicaccess/act_test.go new file mode 100644 index 00000000000..e1d316d2841 --- /dev/null +++ b/pkg/dynamicaccess/act_test.go @@ -0,0 +1,19 @@ +package dynamicaccess + +import "testing" + +func TestAdd(t *testing.T) { + a := NewAct() + _, err := a.Add("", "") + if err != nil { + t.Error("Add() should not return an error") + } +} + +func TestGet(t *testing.T) { + a := NewAct() + _, err := a.Get("") + if err != nil { + t.Error("Get() should not return an error") + } +} diff --git a/pkg/dynamicaccess/container.go b/pkg/dynamicaccess/container.go new file mode 100644 index 00000000000..ae683aa0fcd --- /dev/null +++ b/pkg/dynamicaccess/container.go @@ -0,0 +1,7 @@ +package dynamicaccess + +// iterator +type Container interface { + Add(oldItemKey string, oldRootHash string) (newRootHash string, err error) + Get(rootKey string) (value string, err error) +} diff --git a/pkg/dynamicaccess/controller.go b/pkg/dynamicaccess/controller.go new file mode 100644 index 00000000000..3dfee5bff73 --- /dev/null +++ b/pkg/dynamicaccess/controller.go @@ -0,0 +1,18 @@ +package dynamicaccess + +type Controller interface { +} + +type defaultController struct { + histrory History + uploader Publish + grantee Grantee +} + +func NewController(histrory History, uploader Publish, grantee Grantee) Controller { + return &defaultController{ + histrory: histrory, + uploader: uploader, + grantee: grantee, + } +} diff --git a/pkg/dynamicaccess/diffieHellman.go b/pkg/dynamicaccess/diffieHellman.go new file mode 100644 index 00000000000..8ee9bb1bcce --- /dev/null +++ b/pkg/dynamicaccess/diffieHellman.go @@ -0,0 +1,31 @@ +package dynamicaccess + +import ( + "crypto/ecdsa" + + Crypto "github.com/ethersphere/bee/pkg/crypto" + "github.com/ethersphere/bee/pkg/keystore" + KeyStoreMem "github.com/ethersphere/bee/pkg/keystore/mem" +) + +type DiffieHellman interface { + SharedSecret(pubKey, tag string, moment []byte) (string, error) +} + +type defaultDiffieHellman struct { + key *ecdsa.PrivateKey + keyStoreService keystore.Service + keyStoreEdg keystore.EDG +} + +func (d *defaultDiffieHellman) SharedSecret(pubKey string, tag string, moment []byte) (string, error) { + return "", nil +} + +func NewDiffieHellman(key *ecdsa.PrivateKey) DiffieHellman { + return &defaultDiffieHellman{ + key: key, + keyStoreService: KeyStoreMem.New(), + keyStoreEdg: Crypto.EDGSecp256_K1, + } +} diff --git a/pkg/dynamicaccess/diffieHellman_test.go b/pkg/dynamicaccess/diffieHellman_test.go new file mode 100644 index 00000000000..2e1c870a178 --- /dev/null +++ b/pkg/dynamicaccess/diffieHellman_test.go @@ -0,0 +1,10 @@ +package dynamicaccess + +import "testing" + +func TestSharedSecret(t *testing.T) { + _, err := NewDiffieHellman(nil).SharedSecret("", "", nil) + if err != nil { + t.Errorf("Error generating shared secret: %v", err) + } +} diff --git a/pkg/dynamicaccess/feed.go b/pkg/dynamicaccess/feed.go new file mode 100644 index 00000000000..2576c2733a5 --- /dev/null +++ b/pkg/dynamicaccess/feed.go @@ -0,0 +1,36 @@ +package dynamicaccess + +// referencia: history.go +type Feed interface { + Update(itemKey string, content string) error + Get(itemKey string) (content string, err error) + AddNewGrantee(itemKey string, grantee string) error + RemoveGrantee(itemKey string, grantee string) error + GetAccess(encryptedRef string, publisher string, tag string) (access string, err error) +} + +type defaultFeed struct{} + +func (f *defaultFeed) Update(itemKey string, content string) error { + return nil +} + +func (f *defaultFeed) Get(itemKey string) (content string, err error) { + return "", nil +} + +func (f *defaultFeed) AddNewGrantee(itemKey string, grantee string) error { + return nil +} + +func (f *defaultFeed) RemoveGrantee(itemKey string, grantee string) error { + return nil +} + +func (f *defaultFeed) GetAccess(encryptedRef string, publisher string, tag string) (access string, err error) { + return "", nil +} + +func NewFeed() Feed { + return &defaultFeed{} +} diff --git a/pkg/dynamicaccess/feed_test.go b/pkg/dynamicaccess/feed_test.go new file mode 100644 index 00000000000..1839e6230b9 --- /dev/null +++ b/pkg/dynamicaccess/feed_test.go @@ -0,0 +1,40 @@ +package dynamicaccess + +import "testing" + +func TestFeedUpdate(t *testing.T) { + err := NewFeed().Update("", "") + if err != nil { + t.Errorf("Error updating feed: %v", err) + } +} + +func TestFeedGet(t *testing.T) { + content, err := NewFeed().Get("") + if err != nil { + t.Errorf("Error getting feed: %v", err) + } + _ = content // Ignore the content if not needed +} + +func TestFeedAddNewGrantee(t *testing.T) { + err := NewFeed().AddNewGrantee("", "") + if err != nil { + t.Errorf("Error adding new grantee to feed: %v", err) + } +} + +func TestFeedRemoveGrantee(t *testing.T) { + err := NewFeed().RemoveGrantee("", "") + if err != nil { + t.Errorf("Error removing grantee from feed: %v", err) + } +} + +func TestFeedGetAccess(t *testing.T) { + access, err := NewFeed().GetAccess("", "", "") + if err != nil { + t.Errorf("Error getting access to feed: %v", err) + } + _ = access // Ignore the access if not needed +} diff --git a/pkg/dynamicaccess/grantee.go b/pkg/dynamicaccess/grantee.go new file mode 100644 index 00000000000..27eb1636a09 --- /dev/null +++ b/pkg/dynamicaccess/grantee.go @@ -0,0 +1,26 @@ +package dynamicaccess + +type Grantee interface { + Revoke(topic string) error + RevokeList(topic string, removeList []string, addList []string) (string, error) + Publish(topic string) error +} + +type defaultGrantee struct { +} + +func (g *defaultGrantee) Revoke(topic string) error { + return nil +} + +func (g *defaultGrantee) RevokeList(topic string, removeList []string, addList []string) (string, error) { + return "", nil +} + +func (g *defaultGrantee) Publish(topic string) error { + return nil +} + +func NewGrantee() Grantee { + return &defaultGrantee{} +} diff --git a/pkg/dynamicaccess/grantee_test.go b/pkg/dynamicaccess/grantee_test.go new file mode 100644 index 00000000000..38b2d10b347 --- /dev/null +++ b/pkg/dynamicaccess/grantee_test.go @@ -0,0 +1,24 @@ +package dynamicaccess + +import "testing" + +func TestGranteeRevoke(t *testing.T) { + err := NewGrantee().Revoke("") + if err != nil { + t.Errorf("Error revoking grantee: %v", err) + } +} + +func TestGranteeRevokeList(t *testing.T) { + _, err := NewGrantee().RevokeList("", nil, nil) + if err != nil { + t.Errorf("Error revoking list of grantees: %v", err) + } +} + +func TestGranteePublish(t *testing.T) { + err := NewGrantee().Publish("") + if err != nil { + t.Errorf("Error publishing grantee: %v", err) + } +} diff --git a/pkg/dynamicaccess/history.go b/pkg/dynamicaccess/history.go new file mode 100644 index 00000000000..b9a9438b331 --- /dev/null +++ b/pkg/dynamicaccess/history.go @@ -0,0 +1,22 @@ +package dynamicaccess + +// TODO FROM BEE!!!! +// timestamp alapú history +type History interface { + Add(oldItemKey string, oldRootHash string) (newRootHash string, err error) + Get(rootKey string) (value string, err error) +} + +type defaultHistory struct{} + +func (h *defaultHistory) Add(oldItemKey string, oldRootHash string) (newRootHash string, err error) { + return "", nil +} + +func (h *defaultHistory) Get(rootKey string) (value string, err error) { + return "", nil +} + +func NewHistory() History { + return &defaultHistory{} +} diff --git a/pkg/dynamicaccess/history_test.go b/pkg/dynamicaccess/history_test.go new file mode 100644 index 00000000000..9116e41f548 --- /dev/null +++ b/pkg/dynamicaccess/history_test.go @@ -0,0 +1,19 @@ +package dynamicaccess + +import "testing" + +func TestHistoryAdd(t *testing.T) { + newRootHash, err := NewHistory().Add("", "") + if err != nil { + t.Errorf("Error adding history: %v", err) + } + _ = newRootHash // Ignore the newRootHash if not needed +} + +func TestHistoryGet(t *testing.T) { + value, err := NewHistory().Get("") + if err != nil { + t.Errorf("Error getting history: %v", err) + } + _ = value // Ignore the value if not needed +} diff --git a/pkg/dynamicaccess/mock/accesslogic.go b/pkg/dynamicaccess/mock/accesslogic.go new file mode 100644 index 00000000000..8be8e3a07fd --- /dev/null +++ b/pkg/dynamicaccess/mock/accesslogic.go @@ -0,0 +1,12 @@ +package mock + +type AccessLogicMock struct { + GetFunc func(string, string, string) (string, error) +} + +func (ma *AccessLogicMock) Get(encryped_ref string, publisher string, tag string) (string, error) { + if ma.GetFunc == nil { + return "", nil + } + return ma.GetFunc(encryped_ref, publisher, tag) +} diff --git a/pkg/dynamicaccess/mock/container.go b/pkg/dynamicaccess/mock/container.go new file mode 100644 index 00000000000..3cad9badd39 --- /dev/null +++ b/pkg/dynamicaccess/mock/container.go @@ -0,0 +1,20 @@ +package mock + +type ContainerMock struct { + AddFunc func(string, string, string) error + GetFunc func(string, string, string) (string, error) +} + +func (ma *ContainerMock) Add(ref string, publisher string, tag string) error { + if ma.AddFunc == nil { + return nil + } + return ma.AddFunc(ref, publisher, tag) +} + +func (ma *ContainerMock) Get(ref string, publisher string, tag string) (string, error) { + if ma.GetFunc == nil { + return "", nil + } + return ma.GetFunc(ref, publisher, tag) +} diff --git a/pkg/dynamicaccess/mock/diffieHellman.go b/pkg/dynamicaccess/mock/diffieHellman.go new file mode 100644 index 00000000000..f71131b11ff --- /dev/null +++ b/pkg/dynamicaccess/mock/diffieHellman.go @@ -0,0 +1,12 @@ +package mock + +type DiffieHellmanMock struct { + SharedSecretFunc func(string, string, []byte) (string, error) +} + +func (ma *DiffieHellmanMock) SharedSecret(publicKey string, tag string, moment []byte) (string, error) { + if ma.SharedSecretFunc == nil { + return "", nil + } + return ma.SharedSecretFunc(publicKey, tag, moment) +} diff --git a/pkg/dynamicaccess/publish.go b/pkg/dynamicaccess/publish.go new file mode 100644 index 00000000000..f913288e4d5 --- /dev/null +++ b/pkg/dynamicaccess/publish.go @@ -0,0 +1,12 @@ +package dynamicaccess + +type Publish interface { + upload(ref string) (string, error) +} + +type DefaultPublish struct { +} + +func (d *DefaultPublish) upload(ref string) (string, error) { + return "default", nil +} diff --git a/pkg/dynamicaccess/publish_test.go b/pkg/dynamicaccess/publish_test.go new file mode 100644 index 00000000000..d31069da4a3 --- /dev/null +++ b/pkg/dynamicaccess/publish_test.go @@ -0,0 +1,11 @@ +package dynamicaccess + +import "testing" + +func TestUpload(t *testing.T) { + p := &DefaultPublish{} + _, err := p.upload("test") + if err != nil { + t.Errorf("Error uploading file: %v", err) + } +} diff --git a/pkg/dynamicaccess/timestamp.go b/pkg/dynamicaccess/timestamp.go new file mode 100644 index 00000000000..48347d33a7c --- /dev/null +++ b/pkg/dynamicaccess/timestamp.go @@ -0,0 +1,10 @@ +package dynamicaccess + +// container interface bee-ből a manifest +type Timestamp interface{} + +type defaultTimeStamp struct{} + +func NewTimestamp() Timestamp { + return &defaultTimeStamp{} +} diff --git a/pkg/dynamicaccess/timestamp_test.go b/pkg/dynamicaccess/timestamp_test.go new file mode 100644 index 00000000000..e39ccbcf0c5 --- /dev/null +++ b/pkg/dynamicaccess/timestamp_test.go @@ -0,0 +1 @@ +package dynamicaccess From 3747c33d9f1c29de38e54b1855d1809761868b5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferenc=20S=C3=A1rai?= Date: Wed, 13 Mar 2024 15:43:35 +0100 Subject: [PATCH 02/34] Diffie-Hellman (#3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Use DiffieHellmanMock * Adds a comment about Get * Add support for ECDSA public key in DiffieHellman.SharedSecret function * Update defaultAct implementation * Adds pseudo code for Access Logic * Update default Act creation; Fix basic Act tests * Refactor access logic to use new ActMock implementation * feat(history): test mockups wip * Refactor DiffieHellman implementation * changes pseudocode for Diffie-Hellmann read * Co-authored-by: Bálint Ujvári * DiffieHellman mock generates a real sherd secret * Refactor Act * Adds manifest lookup * Extend act_test * Adds unit tests, some values are mocked * Refactor act mock impl with map[string]map[string]string * Add check mock implementation for DiffieHellman interface * Add Load, Store to Act interface. Refactor Act interface * refactor act, diffieHellman mocks, tests * Add TestLoadStore function to act_test.go * Remove unnecessary code in Load function * Add history mock and History lookup test * Act refactor Co-authored-by: Bálint Ujvári * Refactor Add method to return Act interface * Change Get method return type to []byte --------- Co-authored-by: Ferenc Sárai Co-authored-by: Peter Ott Co-authored-by: Bálint Ujvári Co-authored-by: Levente Kiss Co-authored-by: Roland Seres Co-authored-by: Kexort Co-authored-by: Bálint Ujvári --- pkg/dynamicaccess/accesslogic.go | 146 ++++++++++++++++--- pkg/dynamicaccess/accesslogic_test.go | 177 +++++++++++++++++++++++- pkg/dynamicaccess/act.go | 51 ++++++- pkg/dynamicaccess/act_test.go | 122 ++++++++++++++-- pkg/dynamicaccess/diffieHellman.go | 28 ++-- pkg/dynamicaccess/diffieHellman_test.go | 50 ++++++- pkg/dynamicaccess/feed.go | 36 ----- pkg/dynamicaccess/feed_test.go | 40 ------ pkg/dynamicaccess/history.go | 35 +++-- pkg/dynamicaccess/history_test.go | 65 +++++++-- pkg/dynamicaccess/mock/act.go | 47 +++++++ pkg/dynamicaccess/mock/diffieHellman.go | 20 ++- pkg/dynamicaccess/mock/history.go | 90 ++++++++++++ 13 files changed, 739 insertions(+), 168 deletions(-) delete mode 100644 pkg/dynamicaccess/feed.go delete mode 100644 pkg/dynamicaccess/feed_test.go create mode 100644 pkg/dynamicaccess/mock/act.go create mode 100644 pkg/dynamicaccess/mock/history.go diff --git a/pkg/dynamicaccess/accesslogic.go b/pkg/dynamicaccess/accesslogic.go index 269fe4c4638..ee141d7b69f 100644 --- a/pkg/dynamicaccess/accesslogic.go +++ b/pkg/dynamicaccess/accesslogic.go @@ -1,41 +1,143 @@ package dynamicaccess import ( - "hash" + "context" + "crypto/ecdsa" + "errors" - "github.com/ethersphere/bee/pkg/dynamicaccess/mock" encryption "github.com/ethersphere/bee/pkg/encryption" + file "github.com/ethersphere/bee/pkg/file" + manifest "github.com/ethersphere/bee/pkg/manifest" + "golang.org/x/crypto/sha3" ) +var hashFunc = sha3.NewLegacyKeccak256 + type AccessLogic interface { - Get(encryped_ref string, publisher string, tag string) (string, error) + Get(act_root_hash string, encryped_ref string, publisher string, tag string) (string, error) + GetLookUpKey(publisher string, tag string) (string, error) + GetAccessKeyDecriptionKey(publisher string, tag string) (string, error) + GetEncryptedAccessKey(act_root_hash string, lookup_key string) (manifest.Entry, error) } type DefaultAccessLogic struct { diffieHellman DiffieHellman - encryption encryption.Interface - act Act + //encryption encryption.Interface + act defaultAct +} + +// Will give back Swarm reference with symmertic encryption key (128 byte) +// @publisher: public key +func (al *DefaultAccessLogic) GetLookUpKey(publisher string, tag string) (string, error) { + zeroByteArray := []byte{0} + // Generate lookup key using Diffie Hellman + lookup_key, err := al.diffieHellman.SharedSecret(publisher, tag, zeroByteArray) + if err != nil { + return "", err + } + return lookup_key, nil + +} + +func (al *DefaultAccessLogic) GetAccessKeyDecriptionKey(publisher string, tag string) (string, error) { + oneByteArray := []byte{1} + // Generate access key decryption key using Diffie Hellman + access_key_decryption_key, err := al.diffieHellman.SharedSecret(publisher, tag, oneByteArray) + if err != nil { + return "", err + } + return access_key_decryption_key, nil } -func (al *DefaultAccessLogic) Get(encryped_ref string, publisher string, tag string) (string, error) { - return "", nil +func (al *DefaultAccessLogic) GetEncryptedAccessKey(act_root_hash string, lookup_key string) (manifest.Entry, error) { + if act_root_hash == "" { + return nil, errors.New("no ACT root hash was provided") + } + if lookup_key == "" { + return nil, errors.New("no lookup key") + } + + manifest_raw, err := al.act.Get(act_root_hash) + if err != nil { + return nil, err + } + al.act.Get(act_root_hash) + + // Lookup encrypted access key from the ACT manifest + var loadSaver file.LoadSaver + var ctx context.Context + loadSaver.Load(ctx, []byte(manifest_raw)) // Load the manifest file into loadSaver + //y, err := x.Load(ctx, []byte(manifest_obj)) + manifestObj, err := manifest.NewDefaultManifest(loadSaver, false) + if err != nil { + return nil, err + } + encrypted_access_key, err := manifestObj.Lookup(ctx, lookup_key) + if err != nil { + return nil, err + } + + return encrypted_access_key, nil } -func NewAccessLogic(key encryption.Key, padding int, initCtr uint32, hashFunc func() hash.Hash) AccessLogic { +func (al *DefaultAccessLogic) Get(act_root_hash string, encryped_ref string, publisher string, tag string) (string, error) { + + lookup_key, err := al.GetLookUpKey(publisher, tag) + if err != nil { + return "", err + } + access_key_decryption_key, err := al.GetAccessKeyDecriptionKey(publisher, tag) + if err != nil { + return "", err + } + + // Lookup encrypted access key from the ACT manifest + + encrypted_access_key, err := al.GetEncryptedAccessKey(act_root_hash, lookup_key) + if err != nil { + return "", err + } + + // Decrypt access key + access_key_cipher := encryption.New(encryption.Key(access_key_decryption_key), 4096, uint32(0), hashFunc) + access_key, err := access_key_cipher.Decrypt(encrypted_access_key.Reference().Bytes()) + if err != nil { + return "", err + } + + // Decrypt reference + ref_cipher := encryption.New(access_key, 4096, uint32(0), hashFunc) + ref, err := ref_cipher.Decrypt([]byte(encryped_ref)) + if err != nil { + return "", err + } + + return string(ref), nil +} + +func NewAccessLogic(diffieHellmanPrivateKey *ecdsa.PrivateKey) AccessLogic { return &DefaultAccessLogic{ - diffieHellman: &mock.DiffieHellmanMock{ - SharedSecretFunc: func(publicKey string, tag string, moment []byte) (string, error) { - return publicKey, nil - }, - }, - encryption: encryption.New(key, padding, initCtr, hashFunc), - act: &mock.ContainerMock{ - AddFunc: func(ref string, publisher string, tag string) error { - return nil - }, - GetFunc: func(ref string, publisher string, tag string) (string, error) { - return "", nil - }, - }, + diffieHellman: NewDiffieHellman(diffieHellmanPrivateKey), + //encryption: encryption.New(key, padding, initCtr, hashFunc), + act: defaultAct{}, + + // { + // AddFunc: func(ref string, publisher string, tag string) error { + // return nil + // }, + // GetFunc: func(ref string, publisher string, tag string) (string, error) { + // return "", nil + // }, + // }, } } + +// ------- +// act: &mock.ContainerMock{ +// AddFunc: func(ref string, publisher string, tag string) error { +// return nil +// }, +// GetFunc: func(ref string, publisher string, tag string) (string, error) { +// return "", nil +// }, +// }, diff --git a/pkg/dynamicaccess/accesslogic_test.go b/pkg/dynamicaccess/accesslogic_test.go index 4f3ad6c44bc..cc4ad28ebce 100644 --- a/pkg/dynamicaccess/accesslogic_test.go +++ b/pkg/dynamicaccess/accesslogic_test.go @@ -1,11 +1,176 @@ package dynamicaccess -import "testing" +import ( + "errors" + "testing" -func TestXxx(t *testing.T) { - //key encryption.Key, padding int, initCtr uint32, hashFunc func() hash.Hash - al := NewAccessLogic(nil, 0, 0, nil) - if al == nil { - t.Errorf("Error creating access logic") + "github.com/ethersphere/bee/pkg/crypto" +) + +func setupAccessLogic() AccessLogic { + privateKey, err := crypto.GenerateSecp256k1Key() + if err != nil { + errors.New("error creating private key") + } + al := NewAccessLogic(privateKey) + + return al +} + +func TestGetLookupKey_Success(t *testing.T) { + al := setupAccessLogic() + + publisher := "examplePublisher" + tag := "exampleTag" + + lookupKey, err := al.GetLookUpKey(publisher, tag) + if err != nil { + t.Errorf("Could not fetch lookup key from publisher and tag") + } + + expectedLookupKey := "expectedLookupKey" + if lookupKey != expectedLookupKey { + t.Errorf("The lookup key that was returned is not correct!") + } +} + +func TestGetLookupKey_Error(t *testing.T) { + al := setupAccessLogic() + + invalidPublisher := "" + tag := "exampleTag" + + lookupKey, err := al.GetLookUpKey(invalidPublisher, tag) + if err != nil { + t.Errorf("There was an error while fetching lookup key") + } + + if lookupKey != "" { + t.Errorf("Expected lookup key to be empty for invalid input") + } +} + +func TestGetAccessKeyDecriptionKey_Success(t *testing.T) { + al := setupAccessLogic() + + publisher := "examplePublisher" + tag := "exampleTag" + + access_key_decryption_key, err := al.GetAccessKeyDecriptionKey(publisher, tag) + if err != nil { + t.Errorf("GetAccessKeyDecriptionKey gave back error") + } + + expectedResult := "we-dont-know" + if access_key_decryption_key != expectedResult { + t.Errorf("The access key decryption key is not correct!") + } +} + +func TestGetAccessKeyDecriptionKey_Error(t *testing.T) { + al := setupAccessLogic() + + invalidPublisher := "" + tag := "exampleTag" + + access_key_decryption_key, err := al.GetAccessKeyDecriptionKey(invalidPublisher, tag) + if err != nil { + t.Errorf("GetAccessKeyDecriptionKey gave back error") + } + + if access_key_decryption_key != "" { + t.Errorf("GetAccessKeyDecriptionKey should give back empty string for invalid input!") + } +} + +func TestGetEncryptedAccessKey_Success(t *testing.T) { + al := setupAccessLogic() + + actRootHash := "0xabcexample" + lookupKey := "exampleLookupKey" + + encrypted_access_key, err := al.GetEncryptedAccessKey(actRootHash, lookupKey) + if err != nil { + t.Errorf("There was an error while executing GetEncryptedAccessKey") + } + + expectedEncryptedKey := "abc013encryptedkey" + if encrypted_access_key != expectedEncryptedKey { + t.Errorf("GetEncryptedAccessKey didn't give back the expected value!") + } +} + +func TestGetEncryptedAccessKey_Error(t *testing.T) { + al := setupAccessLogic() + + actRootHash := "0xabcexample" + lookupKey := "exampleLookupKey" + + empty_act_result, _ := al.GetEncryptedAccessKey("", lookupKey) + if empty_act_result != nil { + t.Errorf("GetEncryptedAccessKey should give back nil for empty act root hash!") + } + + empty_lookup_result, _ := al.GetEncryptedAccessKey(actRootHash, "") + + if empty_lookup_result != nil { + t.Errorf("GetEncryptedAccessKey should give back nil for empty lookup key!") + } +} + +func TestGet_Success(t *testing.T) { + al := setupAccessLogic() + + actRootHash := "0xabcexample" + encryptedRef := "bzzabcasab" + publisher := "examplePublisher" + tag := "exampleTag" + + ref, err := al.Get(actRootHash, encryptedRef, publisher, tag) + if err != nil { + t.Errorf("There was an error while calling Get") + } + + expectedRef := "bzzNotEncrypted128long" + if ref != expectedRef { + t.Errorf("Get gave back wrong Swarm reference!") + } +} + +func TestGet_Error(t *testing.T) { + al := setupAccessLogic() + + actRootHash := "0xabcexample" + encryptedRef := "bzzabcasab" + publisher := "examplePublisher" + tag := "exampleTag" + + refOne, _ := al.Get("", encryptedRef, publisher, tag) + if refOne != "" { + t.Errorf("Get should give back empty string if ACT root hash not provided!") + } + + refTwo, _ := al.Get(actRootHash, "", publisher, tag) + if refTwo != "" { + t.Errorf("Get should give back empty string if encrypted ref not provided!") + } + + refThree, _ := al.Get(actRootHash, encryptedRef, "", tag) + if refThree != "" { + t.Errorf("Get should give back empty string if publisher not provided!") + } + + refFour, _ := al.Get(actRootHash, encryptedRef, publisher, "") + if refFour != "" { + t.Errorf("Get should give back empty string if tag was not provided!") + } +} + +func TestNewAccessLogic(t *testing.T) { + logic := setupAccessLogic() + + _, ok := logic.(*DefaultAccessLogic) + if !ok { + t.Errorf("NewAccessLogic: expected type *DefaultAccessLogic, got %T", logic) } } diff --git a/pkg/dynamicaccess/act.go b/pkg/dynamicaccess/act.go index b3448260312..b63b1dafc6b 100644 --- a/pkg/dynamicaccess/act.go +++ b/pkg/dynamicaccess/act.go @@ -1,18 +1,55 @@ package dynamicaccess -type Act interface{} +import ( + "encoding/hex" + + "github.com/ethersphere/bee/pkg/manifest" + "github.com/ethersphere/bee/pkg/swarm" +) + +type Act interface { + Add(lookupKey []byte, encryptedAccessKey []byte) Act + Get(lookupKey []byte) []byte + Load(lookupKey []byte) manifest.Entry + Store(me manifest.Entry) +} + +var _ Act = (*defaultAct)(nil) type defaultAct struct { + container map[string]string +} + +func (act *defaultAct) Add(lookupKey []byte, encryptedAccessKey []byte) Act { + act.container[hex.EncodeToString(lookupKey)] = hex.EncodeToString(encryptedAccessKey) + return act +} + +func (act *defaultAct) Get(lookupKey []byte) []byte { + if key, ok := act.container[hex.EncodeToString(lookupKey)]; ok { + bytes, err := hex.DecodeString(key) + if err == nil { + return bytes + } + } + return make([]byte, 0) } -func (a *defaultAct) Add(oldItemKey string, oldRootHash string) (newRootHash string, err error) { - return "", nil +// to manifestEntry +func (act *defaultAct) Load(lookupKey []byte) manifest.Entry { + return manifest.NewEntry(swarm.NewAddress(lookupKey), act.container) } -func (a *defaultAct) Get(rootKey string) (value string, err error) { - return "", nil +// from manifestEntry +func (act *defaultAct) Store(me manifest.Entry) { + if act.container == nil { + act.container = make(map[string]string) + } + act.container = me.Metadata() } -func NewAct() Container { - return &defaultAct{} +func NewDefaultAct() Act { + return &defaultAct{ + container: make(map[string]string), + } } diff --git a/pkg/dynamicaccess/act_test.go b/pkg/dynamicaccess/act_test.go index e1d316d2841..0e5ca853f0b 100644 --- a/pkg/dynamicaccess/act_test.go +++ b/pkg/dynamicaccess/act_test.go @@ -1,19 +1,119 @@ -package dynamicaccess +package dynamicaccess_test -import "testing" +import ( + "bytes" + "context" + "encoding/hex" + "testing" -func TestAdd(t *testing.T) { - a := NewAct() - _, err := a.Add("", "") - if err != nil { - t.Error("Add() should not return an error") + "github.com/ethersphere/bee/pkg/dynamicaccess" + "github.com/ethersphere/bee/pkg/file/loadsave" + "github.com/ethersphere/bee/pkg/file/pipeline" + "github.com/ethersphere/bee/pkg/file/pipeline/builder" + "github.com/ethersphere/bee/pkg/file/redundancy" + "github.com/ethersphere/bee/pkg/manifest" + "github.com/ethersphere/bee/pkg/storage" + mockstorer "github.com/ethersphere/bee/pkg/storer/mock" + "github.com/ethersphere/bee/pkg/swarm" +) + +func TestActAddGet(t *testing.T) { + act := dynamicaccess.NewDefaultAct() + lookupKey := swarm.RandAddress(t).Bytes() + encryptedAccesskey := swarm.RandAddress(t).Bytes() + act2 := act.Add(lookupKey, encryptedAccesskey) + if act2 == nil { + t.Error("Add() should return an act") + } + + key := act.Get(lookupKey) + if !bytes.Equal(key, encryptedAccesskey) { + t.Errorf("Get() value is not the expected %s != %s", key, encryptedAccesskey) } } -func TestGet(t *testing.T) { - a := NewAct() - _, err := a.Get("") +func TestActWithManifest(t *testing.T) { + + storer := mockstorer.New() + encrypt := false + ctx := context.Background() + ls := loadsave.New(storer.ChunkStore(), storer.Cache(), pipelineFactory(storer.Cache(), false, 0)) + rootManifest, err := manifest.NewDefaultManifest(ls, encrypt) + if err != nil { + t.Error("DefaultManifest should not return an error") + } + + act := dynamicaccess.NewDefaultAct() + lookupKey := swarm.RandAddress(t).Bytes() + encryptedAccesskey := swarm.RandAddress(t).Bytes() + act2 := act.Add(lookupKey, encryptedAccesskey) + if act2 == nil { + t.Error("Add() should return an act") + } + + actManifEntry := act.Load(lookupKey) + if actManifEntry == nil { + t.Error("Load() should return a manifest.Entry") + } + + err = rootManifest.Add(ctx, hex.EncodeToString(lookupKey), actManifEntry) + if err != nil { + t.Error("rootManifest.Add() should not return an error") + } + + _, err = rootManifest.Store(ctx) + if err != nil { + t.Error("rootManifest.Store() should not return an error") + } + + actualMe, err := rootManifest.Lookup(ctx, hex.EncodeToString(lookupKey)) if err != nil { - t.Error("Get() should not return an error") + t.Error("rootManifest.Lookup() should not return an error") + } + + actualAct := dynamicaccess.NewDefaultAct() + actualAct.Store(actualMe) + actualEak := actualAct.Get(lookupKey) + if !bytes.Equal(actualEak, encryptedAccesskey) { + t.Errorf("actualAct.Store() value is not the expected %s != %s", actualEak, encryptedAccesskey) + } +} + +func TestActStore(t *testing.T) { + mp := make(map[string]string) + + lookupKey := swarm.RandAddress(t).Bytes() + encryptedAccesskey := swarm.RandAddress(t).Bytes() + mp[hex.EncodeToString(lookupKey)] = hex.EncodeToString(encryptedAccesskey) + + me := manifest.NewEntry(swarm.NewAddress(lookupKey), mp) + act := dynamicaccess.NewDefaultAct() + act.Store(me) + eak := act.Get(lookupKey) + + if !bytes.Equal(eak, encryptedAccesskey) { + t.Errorf("Store() value is not the expected %s != %s", eak, encryptedAccesskey) + } + +} + +func TestActLoad(t *testing.T) { + act := dynamicaccess.NewDefaultAct() + lookupKey := swarm.RandAddress(t).Bytes() + encryptedAccesskey := swarm.RandAddress(t).Bytes() + act.Add(lookupKey, encryptedAccesskey) + me := act.Load(lookupKey) + + eak := me.Metadata()[hex.EncodeToString(lookupKey)] + + if eak != hex.EncodeToString(encryptedAccesskey) { + t.Errorf("Load() value is not the expected %s != %s", eak, encryptedAccesskey) + } + +} + +func pipelineFactory(s storage.Putter, encrypt bool, rLevel redundancy.Level) func() pipeline.Interface { + return func() pipeline.Interface { + return builder.NewPipelineBuilder(context.Background(), s, encrypt, rLevel) } } diff --git a/pkg/dynamicaccess/diffieHellman.go b/pkg/dynamicaccess/diffieHellman.go index 8ee9bb1bcce..a9ff7f7f8cd 100644 --- a/pkg/dynamicaccess/diffieHellman.go +++ b/pkg/dynamicaccess/diffieHellman.go @@ -2,30 +2,30 @@ package dynamicaccess import ( "crypto/ecdsa" + "errors" - Crypto "github.com/ethersphere/bee/pkg/crypto" - "github.com/ethersphere/bee/pkg/keystore" - KeyStoreMem "github.com/ethersphere/bee/pkg/keystore/mem" + "github.com/ethersphere/bee/pkg/crypto" ) type DiffieHellman interface { - SharedSecret(pubKey, tag string, moment []byte) (string, error) + SharedSecret(publicKey *ecdsa.PublicKey, tag string, moment []byte) ([]byte, error) // tag- topic? } +var _ DiffieHellman = (*defaultDiffieHellman)(nil) + type defaultDiffieHellman struct { - key *ecdsa.PrivateKey - keyStoreService keystore.Service - keyStoreEdg keystore.EDG + key *ecdsa.PrivateKey } -func (d *defaultDiffieHellman) SharedSecret(pubKey string, tag string, moment []byte) (string, error) { - return "", nil +func (dh *defaultDiffieHellman) SharedSecret(publicKey *ecdsa.PublicKey, tag string, salt []byte) ([]byte, error) { + x, _ := publicKey.Curve.ScalarMult(publicKey.X, publicKey.Y, dh.key.D.Bytes()) + if x == nil { + return nil, errors.New("shared secret is point at infinity") + } + return crypto.LegacyKeccak256(append(x.Bytes(), salt...)) } func NewDiffieHellman(key *ecdsa.PrivateKey) DiffieHellman { - return &defaultDiffieHellman{ - key: key, - keyStoreService: KeyStoreMem.New(), - keyStoreEdg: Crypto.EDGSecp256_K1, - } + return &defaultDiffieHellman{key: key} + } diff --git a/pkg/dynamicaccess/diffieHellman_test.go b/pkg/dynamicaccess/diffieHellman_test.go index 2e1c870a178..5c80592b3ec 100644 --- a/pkg/dynamicaccess/diffieHellman_test.go +++ b/pkg/dynamicaccess/diffieHellman_test.go @@ -1,10 +1,54 @@ -package dynamicaccess +package dynamicaccess_test -import "testing" +import ( + "bytes" + "crypto/rand" + "encoding/hex" + "io" + "testing" + + "github.com/ethersphere/bee/pkg/crypto" + "github.com/ethersphere/bee/pkg/dynamicaccess" +) func TestSharedSecret(t *testing.T) { - _, err := NewDiffieHellman(nil).SharedSecret("", "", nil) + pk, _ := crypto.GenerateSecp256k1Key() + _, err := dynamicaccess.NewDiffieHellman(pk).SharedSecret(&pk.PublicKey, "", nil) if err != nil { t.Errorf("Error generating shared secret: %v", err) } } + +func TestECDHCorrect(t *testing.T) { + t.Parallel() + + key1, err := crypto.GenerateSecp256k1Key() + if err != nil { + t.Fatal(err) + } + dh1 := dynamicaccess.NewDiffieHellman(key1) + + key2, err := crypto.GenerateSecp256k1Key() + if err != nil { + t.Fatal(err) + } + dh2 := dynamicaccess.NewDiffieHellman(key2) + + moment := make([]byte, 1) + if _, err := io.ReadFull(rand.Reader, moment); err != nil { + t.Fatal(err) + } + + shared1, err := dh1.SharedSecret(&key2.PublicKey, "", moment) + if err != nil { + t.Fatal(err) + } + shared2, err := dh2.SharedSecret(&key1.PublicKey, "", moment) + if err != nil { + t.Fatal(err) + } + + if !bytes.Equal(shared1, shared2) { + t.Fatalf("shared secrets do not match %s, %s", hex.EncodeToString(shared1), hex.EncodeToString(shared2)) + } +} diff --git a/pkg/dynamicaccess/feed.go b/pkg/dynamicaccess/feed.go deleted file mode 100644 index 2576c2733a5..00000000000 --- a/pkg/dynamicaccess/feed.go +++ /dev/null @@ -1,36 +0,0 @@ -package dynamicaccess - -// referencia: history.go -type Feed interface { - Update(itemKey string, content string) error - Get(itemKey string) (content string, err error) - AddNewGrantee(itemKey string, grantee string) error - RemoveGrantee(itemKey string, grantee string) error - GetAccess(encryptedRef string, publisher string, tag string) (access string, err error) -} - -type defaultFeed struct{} - -func (f *defaultFeed) Update(itemKey string, content string) error { - return nil -} - -func (f *defaultFeed) Get(itemKey string) (content string, err error) { - return "", nil -} - -func (f *defaultFeed) AddNewGrantee(itemKey string, grantee string) error { - return nil -} - -func (f *defaultFeed) RemoveGrantee(itemKey string, grantee string) error { - return nil -} - -func (f *defaultFeed) GetAccess(encryptedRef string, publisher string, tag string) (access string, err error) { - return "", nil -} - -func NewFeed() Feed { - return &defaultFeed{} -} diff --git a/pkg/dynamicaccess/feed_test.go b/pkg/dynamicaccess/feed_test.go deleted file mode 100644 index 1839e6230b9..00000000000 --- a/pkg/dynamicaccess/feed_test.go +++ /dev/null @@ -1,40 +0,0 @@ -package dynamicaccess - -import "testing" - -func TestFeedUpdate(t *testing.T) { - err := NewFeed().Update("", "") - if err != nil { - t.Errorf("Error updating feed: %v", err) - } -} - -func TestFeedGet(t *testing.T) { - content, err := NewFeed().Get("") - if err != nil { - t.Errorf("Error getting feed: %v", err) - } - _ = content // Ignore the content if not needed -} - -func TestFeedAddNewGrantee(t *testing.T) { - err := NewFeed().AddNewGrantee("", "") - if err != nil { - t.Errorf("Error adding new grantee to feed: %v", err) - } -} - -func TestFeedRemoveGrantee(t *testing.T) { - err := NewFeed().RemoveGrantee("", "") - if err != nil { - t.Errorf("Error removing grantee from feed: %v", err) - } -} - -func TestFeedGetAccess(t *testing.T) { - access, err := NewFeed().GetAccess("", "", "") - if err != nil { - t.Errorf("Error getting access to feed: %v", err) - } - _ = access // Ignore the access if not needed -} diff --git a/pkg/dynamicaccess/history.go b/pkg/dynamicaccess/history.go index b9a9438b331..e2705fffeac 100644 --- a/pkg/dynamicaccess/history.go +++ b/pkg/dynamicaccess/history.go @@ -1,22 +1,35 @@ package dynamicaccess -// TODO FROM BEE!!!! -// timestamp alapú history +import ( + "github.com/ethereum/go-ethereum/common" +) + type History interface { - Add(oldItemKey string, oldRootHash string) (newRootHash string, err error) - Get(rootKey string) (value string, err error) + Add(timestamp int64, act Act) error + Get(timestamp int64) (Act, error) + Lookup(at int64) (Act, error) +} + +var _ History = (*history)(nil) + +type history struct { + history map[int64]*Act +} + +func NewHistory(topic []byte, owner common.Address) *history { + return &history{history: make(map[int64]*Act)} } -type defaultHistory struct{} +func (h *history) Add(timestamp int64, act Act) error { -func (h *defaultHistory) Add(oldItemKey string, oldRootHash string) (newRootHash string, err error) { - return "", nil + return nil } -func (h *defaultHistory) Get(rootKey string) (value string, err error) { - return "", nil +func (h *history) Lookup(at int64) (Act, error) { + return nil, nil } -func NewHistory() History { - return &defaultHistory{} +func (h *history) Get(timestamp int64) (Act, error) { + // get the feed + return nil, nil } diff --git a/pkg/dynamicaccess/history_test.go b/pkg/dynamicaccess/history_test.go index 9116e41f548..2a24e65d862 100644 --- a/pkg/dynamicaccess/history_test.go +++ b/pkg/dynamicaccess/history_test.go @@ -1,19 +1,58 @@ -package dynamicaccess +package dynamicaccess_test -import "testing" +import ( + "encoding/hex" + "testing" + "time" -func TestHistoryAdd(t *testing.T) { - newRootHash, err := NewHistory().Add("", "") - if err != nil { - t.Errorf("Error adding history: %v", err) + "github.com/ethersphere/bee/pkg/dynamicaccess" + "github.com/ethersphere/bee/pkg/dynamicaccess/mock" + "github.com/stretchr/testify/assert" +) + +func TestHistoryLookup(t *testing.T) { + h := pretareTestHistory() + now := time.Now() + + tests := []struct { + input int64 + expected string + }{ + {input: 0, expected: "value3"}, + {input: now.Unix(), expected: "value3"}, + {input: now.AddDate(0, -5, 0).Unix(), expected: "value3"}, + {input: now.AddDate(0, -6, 0).Unix(), expected: "value3"}, + {input: now.AddDate(-1, 0, 0).Unix(), expected: "value3"}, + {input: now.AddDate(-1, -6, 0).Unix(), expected: "value2"}, + {input: now.AddDate(-2, -0, 0).Unix(), expected: "value2"}, + {input: now.AddDate(-2, -6, 0).Unix(), expected: "value1"}, + {input: now.AddDate(-3, -0, 0).Unix(), expected: "value1"}, } - _ = newRootHash // Ignore the newRootHash if not needed -} -func TestHistoryGet(t *testing.T) { - value, err := NewHistory().Get("") - if err != nil { - t.Errorf("Error getting history: %v", err) + for _, tt := range tests { + t.Run("", func(t *testing.T) { + actAt, _ := h.Lookup(tt.input) + output := actAt.Get([]byte("key1")) + assert.Equal(t, output, hex.EncodeToString([]byte(tt.expected))) + }) } - _ = value // Ignore the value if not needed +} + +func pretareTestHistory() dynamicaccess.History { + var ( + h = mock.NewHistory() + now = time.Now() + act1 = dynamicaccess.NewDefaultAct() + act2 = dynamicaccess.NewDefaultAct() + act3 = dynamicaccess.NewDefaultAct() + ) + act1.Add([]byte("key1"), []byte("value1")) + act2.Add([]byte("key1"), []byte("value2")) + act3.Add([]byte("key1"), []byte("value3")) + + h.Insert(now.AddDate(-3, 0, 0).Unix(), act1) + h.Insert(now.AddDate(-2, 0, 0).Unix(), act2) + h.Insert(now.AddDate(-1, 0, 0).Unix(), act3) + + return h } diff --git a/pkg/dynamicaccess/mock/act.go b/pkg/dynamicaccess/mock/act.go new file mode 100644 index 00000000000..1fd68caa399 --- /dev/null +++ b/pkg/dynamicaccess/mock/act.go @@ -0,0 +1,47 @@ +package mock + +import ( + "github.com/ethersphere/bee/pkg/dynamicaccess" + "github.com/ethersphere/bee/pkg/manifest" +) + +type ActMock struct { + AddFunc func(lookupKey []byte, encryptedAccessKey []byte) dynamicaccess.Act + GetFunc func(lookupKey []byte) []byte + LoadFunc func(lookupKey []byte) manifest.Entry + StoreFunc func(me manifest.Entry) +} + +var _ dynamicaccess.Act = (*ActMock)(nil) + +func (act *ActMock) Add(lookupKey []byte, encryptedAccessKey []byte) dynamicaccess.Act { + if act.AddFunc == nil { + return act + } + return act.AddFunc(lookupKey, encryptedAccessKey) +} + +func (act *ActMock) Get(lookupKey []byte) []byte { + if act.GetFunc == nil { + return make([]byte, 0) + } + return act.GetFunc(lookupKey) +} + +func (act *ActMock) Load(lookupKey []byte) manifest.Entry { + if act.LoadFunc == nil { + return nil + } + return act.LoadFunc(lookupKey) +} + +func (act *ActMock) Store(me manifest.Entry) { + if act.StoreFunc == nil { + return + } + act.StoreFunc(me) +} + +func NewActMock() dynamicaccess.Act { + return &ActMock{} +} diff --git a/pkg/dynamicaccess/mock/diffieHellman.go b/pkg/dynamicaccess/mock/diffieHellman.go index f71131b11ff..91601026893 100644 --- a/pkg/dynamicaccess/mock/diffieHellman.go +++ b/pkg/dynamicaccess/mock/diffieHellman.go @@ -1,12 +1,22 @@ package mock +import ( + "crypto/ecdsa" +) + type DiffieHellmanMock struct { - SharedSecretFunc func(string, string, []byte) (string, error) + SharedSecretFunc func(publicKey *ecdsa.PublicKey, tag string, salt []byte) ([]byte, error) + key *ecdsa.PrivateKey } -func (ma *DiffieHellmanMock) SharedSecret(publicKey string, tag string, moment []byte) (string, error) { - if ma.SharedSecretFunc == nil { - return "", nil +func (dhm *DiffieHellmanMock) SharedSecret(publicKey *ecdsa.PublicKey, tag string, salt []byte) ([]byte, error) { + if dhm.SharedSecretFunc == nil { + return nil, nil } - return ma.SharedSecretFunc(publicKey, tag, moment) + return dhm.SharedSecretFunc(publicKey, tag, salt) + +} + +func NewDiffieHellmanMock(key *ecdsa.PrivateKey) *DiffieHellmanMock { + return &DiffieHellmanMock{key: key} } diff --git a/pkg/dynamicaccess/mock/history.go b/pkg/dynamicaccess/mock/history.go new file mode 100644 index 00000000000..68559c2277a --- /dev/null +++ b/pkg/dynamicaccess/mock/history.go @@ -0,0 +1,90 @@ +package mock + +import ( + "context" + "sort" + "time" + + "github.com/ethersphere/bee/pkg/crypto" + "github.com/ethersphere/bee/pkg/dynamicaccess" + "github.com/ethersphere/bee/pkg/feeds" + "github.com/ethersphere/bee/pkg/storage" + "github.com/ethersphere/bee/pkg/swarm" +) + +type historyMock struct { + history map[int64]dynamicaccess.Act +} + +func NewHistory() *historyMock { + return &historyMock{history: make(map[int64]dynamicaccess.Act)} +} + +func (h *historyMock) Add(timestamp int64, act dynamicaccess.Act) error { + h.history[timestamp] = act + return nil +} + +func (h *historyMock) Insert(timestamp int64, act dynamicaccess.Act) *historyMock { + h.Add(timestamp, act) + return h +} + +func (h *historyMock) Lookup(at int64) (dynamicaccess.Act, error) { + keys := []int64{} + for k := range h.history { + keys = append(keys, k) + } + + sort.Slice(keys, func(i, j int) bool { return keys[i] < keys[j] }) + + timestamp := time.Now() + if at != 0 { + timestamp = time.Unix(at, 0) + } + + for i := len(keys) - 1; i >= 0; i-- { + update := time.Unix(keys[i], 0) + if update.Before(timestamp) || update.Equal(timestamp) { + return h.history[keys[i]], nil + } + } + return nil, nil +} + +func (h *historyMock) Get(timestamp int64) (dynamicaccess.Act, error) { + return h.history[timestamp], nil +} + +type finder struct { + getter *feeds.Getter +} + +type updater struct { + *feeds.Putter + next uint64 +} + +func (f *finder) At(ctx context.Context, at int64, after uint64) (chunk swarm.Chunk, currentIndex, nextIndex feeds.Index, err error) { + return nil, nil, nil, nil +} + +func HistoryFinder(getter storage.Getter, feed *feeds.Feed) feeds.Lookup { + return &finder{feeds.NewGetter(getter, feed)} +} + +func (u *updater) Update(ctx context.Context, at int64, payload []byte) error { + return nil +} + +func (u *updater) Feed() *feeds.Feed { + return nil +} + +func HistoryUpdater(putter storage.Putter, signer crypto.Signer, topic []byte) (feeds.Updater, error) { + p, err := feeds.NewPutter(putter, signer, topic) + if err != nil { + return nil, err + } + return &updater{Putter: p}, nil +} From 4aa8a54b08cd53e80179dc9e16e415289d56baaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Ar=C3=A1nyi?= Date: Wed, 13 Mar 2024 19:07:06 +0400 Subject: [PATCH 03/34] Acces Logic (#8) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Use DiffieHellmanMock * Adds a comment about Get * Add support for ECDSA public key in DiffieHellman.SharedSecret function * Update defaultAct implementation * Adds pseudo code for Access Logic * Update default Act creation; Fix basic Act tests * Refactor access logic to use new ActMock implementation * feat(history): test mockups wip * Refactor DiffieHellman implementation * changes pseudocode for Diffie-Hellmann read * Co-authored-by: Bálint Ujvári * DiffieHellman mock generates a real sherd secret * Refactor Act * Adds manifest lookup * Extend act_test * Adds unit tests, some values are mocked * Refactor act mock impl with map[string]map[string]string * Add check mock implementation for DiffieHellman interface * started Add * changed some sig * save * new grantee addition handling * mod * changed helper function visibilities * some mod with grantee * test mod * save * no error in actInit * Add_New_Grantee_To_Content * comment * copied act_test.go * no compiler errors on our side * Adds Add_New_Grantee_To_Content and ActInit * almost complete grantee container * maybe complete grantee container * Solves merge conflict * access-logic-merge * fix merge issues --- pkg/dynamicaccess/accesslogic.go | 123 ++++++++++++++++++-------- pkg/dynamicaccess/accesslogic_test.go | 115 ++++++++++++++++++------ pkg/dynamicaccess/grantee.go | 48 ++++++++-- pkg/dynamicaccess/grantee_test.go | 102 ++++++++++++++++++--- 4 files changed, 304 insertions(+), 84 deletions(-) diff --git a/pkg/dynamicaccess/accesslogic.go b/pkg/dynamicaccess/accesslogic.go index ee141d7b69f..803c1110517 100644 --- a/pkg/dynamicaccess/accesslogic.go +++ b/pkg/dynamicaccess/accesslogic.go @@ -8,16 +8,22 @@ import ( encryption "github.com/ethersphere/bee/pkg/encryption" file "github.com/ethersphere/bee/pkg/file" manifest "github.com/ethersphere/bee/pkg/manifest" + "github.com/ethersphere/bee/pkg/swarm" "golang.org/x/crypto/sha3" ) var hashFunc = sha3.NewLegacyKeccak256 type AccessLogic interface { - Get(act_root_hash string, encryped_ref string, publisher string, tag string) (string, error) - GetLookUpKey(publisher string, tag string) (string, error) - GetAccessKeyDecriptionKey(publisher string, tag string) (string, error) - GetEncryptedAccessKey(act_root_hash string, lookup_key string) (manifest.Entry, error) + Get(act *Act, encryped_ref swarm.Address, publisher ecdsa.PublicKey, tag string) (string, error) + //Add(act *Act, ref string, publisher ecdsa.PublicKey, tag string) (string, error) + getLookUpKey(publisher ecdsa.PublicKey, tag string) (string, error) + getAccessKeyDecriptionKey(publisher ecdsa.PublicKey, tag string) (string, error) + getEncryptedAccessKey(act Act, lookup_key string) (manifest.Entry, error) + //createEncryptedAccessKey(ref string) + Add_New_Grantee_To_Content(act *Act, encryptedRef swarm.Address, publisherPubKey ecdsa.PublicKey, granteePubKey ecdsa.PublicKey) (*Act, error) + ActInit(ref swarm.Address, publisher ecdsa.PublicKey, tag string) (*Act, swarm.Address, error) + // CreateAccessKey() } type DefaultAccessLogic struct { @@ -26,42 +32,95 @@ type DefaultAccessLogic struct { act defaultAct } -// Will give back Swarm reference with symmertic encryption key (128 byte) -// @publisher: public key -func (al *DefaultAccessLogic) GetLookUpKey(publisher string, tag string) (string, error) { +// Will create a new Act list with only one element (the creator), and will also create encrypted_ref +func (al *DefaultAccessLogic) ActInit(ref swarm.Address, publisher ecdsa.PublicKey, tag string) (*Act, swarm.Address, error) { + act := NewDefaultAct() + + lookup_key, _ := al.getLookUpKey(publisher, "") + access_key_encryption_key, _ := al.getAccessKeyDecriptionKey(publisher, "") + + access_key_cipher := encryption.New(encryption.Key(access_key_encryption_key), 0, uint32(0), hashFunc) + access_key := encryption.GenerateRandomKey(encryption.KeyLength) + encrypted_access_key, _ := access_key_cipher.Encrypt([]byte(access_key)) + + ref_cipher := encryption.New(access_key, 0, uint32(0), hashFunc) + encrypted_ref, _ := ref_cipher.Encrypt(ref.Bytes()) + + act.Add([]byte(lookup_key), encrypted_access_key) + + return &act, swarm.NewAddress(encrypted_ref), nil +} + +// publisher is public key +func (al *DefaultAccessLogic) Add_New_Grantee_To_Content(act *Act, encryptedRef swarm.Address, publisherPubKey ecdsa.PublicKey, granteePubKey ecdsa.PublicKey) (*Act, error) { + + // error handling no encrypted_ref + + // 2 Diffie-Hellman for the publisher (the Creator) + publisher_lookup_key, _ := al.getLookUpKey(publisherPubKey, "") + publisher_ak_decryption_key, _ := al.getAccessKeyDecriptionKey(publisherPubKey, "") + + // Get previously generated access key + access_key_decryption_cipher := encryption.New(encryption.Key(publisher_ak_decryption_key), 0, uint32(0), hashFunc) + encrypted_ak, _ := al.getEncryptedAccessKey(*act, publisher_lookup_key) + access_key, _ := access_key_decryption_cipher.Decrypt(encrypted_ak.Reference().Bytes()) + + // --Encrypt access key for new Grantee-- + + // 2 Diffie-Hellman for the Grantee + lookup_key, _ := al.getLookUpKey(granteePubKey, "") + access_key_encryption_key, _ := al.getAccessKeyDecriptionKey(granteePubKey, "") + + // Encrypt the access key for the new Grantee + cipher := encryption.New(encryption.Key(access_key_encryption_key), 0, uint32(0), hashFunc) + granteeEncryptedAccessKey, _ := cipher.Encrypt(access_key) + // Add the new encrypted access key for the Act + actObj := *act + actObj.Add([]byte(lookup_key), granteeEncryptedAccessKey) + + return &actObj, nil + +} + +// +// act[lookupKey] := valamilyen_cipher.Encrypt(access_key) + +// end of pseudo code like code + +// func (al *DefaultAccessLogic) CreateAccessKey(reference string) { +// } + +func (al *DefaultAccessLogic) getLookUpKey(publisher ecdsa.PublicKey, tag string) (string, error) { zeroByteArray := []byte{0} // Generate lookup key using Diffie Hellman - lookup_key, err := al.diffieHellman.SharedSecret(publisher, tag, zeroByteArray) + lookup_key, err := al.diffieHellman.SharedSecret(&publisher, tag, zeroByteArray) if err != nil { return "", err } - return lookup_key, nil + return string(lookup_key), nil } -func (al *DefaultAccessLogic) GetAccessKeyDecriptionKey(publisher string, tag string) (string, error) { +func (al *DefaultAccessLogic) getAccessKeyDecriptionKey(publisher ecdsa.PublicKey, tag string) (string, error) { oneByteArray := []byte{1} // Generate access key decryption key using Diffie Hellman - access_key_decryption_key, err := al.diffieHellman.SharedSecret(publisher, tag, oneByteArray) + access_key_decryption_key, err := al.diffieHellman.SharedSecret(&publisher, tag, oneByteArray) if err != nil { return "", err } - return access_key_decryption_key, nil + return string(access_key_decryption_key), nil } -func (al *DefaultAccessLogic) GetEncryptedAccessKey(act_root_hash string, lookup_key string) (manifest.Entry, error) { - if act_root_hash == "" { +func (al *DefaultAccessLogic) getEncryptedAccessKey(act Act, lookup_key string) (manifest.Entry, error) { + if act == nil { return nil, errors.New("no ACT root hash was provided") } if lookup_key == "" { return nil, errors.New("no lookup key") } - manifest_raw, err := al.act.Get(act_root_hash) - if err != nil { - return nil, err - } - al.act.Get(act_root_hash) + manifest_raw := act.Get([]byte(lookup_key)) + //al.act.Get(act_root_hash) // Lookup encrypted access key from the ACT manifest var loadSaver file.LoadSaver @@ -80,20 +139,20 @@ func (al *DefaultAccessLogic) GetEncryptedAccessKey(act_root_hash string, lookup return encrypted_access_key, nil } -func (al *DefaultAccessLogic) Get(act_root_hash string, encryped_ref string, publisher string, tag string) (string, error) { +func (al *DefaultAccessLogic) Get(act *Act, encryped_ref swarm.Address, publisher ecdsa.PublicKey, tag string) (string, error) { - lookup_key, err := al.GetLookUpKey(publisher, tag) + lookup_key, err := al.getLookUpKey(publisher, tag) if err != nil { return "", err } - access_key_decryption_key, err := al.GetAccessKeyDecriptionKey(publisher, tag) + access_key_decryption_key, err := al.getAccessKeyDecriptionKey(publisher, tag) if err != nil { return "", err } // Lookup encrypted access key from the ACT manifest - encrypted_access_key, err := al.GetEncryptedAccessKey(act_root_hash, lookup_key) + encrypted_access_key, err := al.getEncryptedAccessKey(*act, lookup_key) if err != nil { return "", err } @@ -107,7 +166,7 @@ func (al *DefaultAccessLogic) Get(act_root_hash string, encryped_ref string, pub // Decrypt reference ref_cipher := encryption.New(access_key, 4096, uint32(0), hashFunc) - ref, err := ref_cipher.Decrypt([]byte(encryped_ref)) + ref, err := ref_cipher.Decrypt(encryped_ref.Bytes()) if err != nil { return "", err } @@ -115,20 +174,10 @@ func (al *DefaultAccessLogic) Get(act_root_hash string, encryped_ref string, pub return string(ref), nil } -func NewAccessLogic(diffieHellmanPrivateKey *ecdsa.PrivateKey) AccessLogic { +func NewAccessLogic(diffieHellman DiffieHellman) AccessLogic { return &DefaultAccessLogic{ - diffieHellman: NewDiffieHellman(diffieHellmanPrivateKey), - //encryption: encryption.New(key, padding, initCtr, hashFunc), - act: defaultAct{}, - - // { - // AddFunc: func(ref string, publisher string, tag string) error { - // return nil - // }, - // GetFunc: func(ref string, publisher string, tag string) (string, error) { - // return "", nil - // }, - // }, + diffieHellman: diffieHellman, + act: defaultAct{}, } } diff --git a/pkg/dynamicaccess/accesslogic_test.go b/pkg/dynamicaccess/accesslogic_test.go index cc4ad28ebce..ee7c16c43e0 100644 --- a/pkg/dynamicaccess/accesslogic_test.go +++ b/pkg/dynamicaccess/accesslogic_test.go @@ -1,10 +1,15 @@ package dynamicaccess import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" "errors" + "fmt" "testing" "github.com/ethersphere/bee/pkg/crypto" + "github.com/ethersphere/bee/pkg/swarm" ) func setupAccessLogic() AccessLogic { @@ -12,7 +17,8 @@ func setupAccessLogic() AccessLogic { if err != nil { errors.New("error creating private key") } - al := NewAccessLogic(privateKey) + diffieHellman := NewDiffieHellman(privateKey) + al := NewAccessLogic(diffieHellman) return al } @@ -20,27 +26,33 @@ func setupAccessLogic() AccessLogic { func TestGetLookupKey_Success(t *testing.T) { al := setupAccessLogic() - publisher := "examplePublisher" + id0, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + // ! this will be random, we can not know the lookup key for a randomly generated key + act, encryptedRef, _ := al.ActInit(swarm.NewAddress([]byte("42")), id0.PublicKey, "") + fmt.Println(act, encryptedRef) + tag := "exampleTag" - lookupKey, err := al.GetLookUpKey(publisher, tag) + lookupKey, err := al.getLookUpKey(id0.PublicKey, tag) if err != nil { t.Errorf("Could not fetch lookup key from publisher and tag") } expectedLookupKey := "expectedLookupKey" if lookupKey != expectedLookupKey { + fmt.Println(string(lookupKey)) t.Errorf("The lookup key that was returned is not correct!") } } -func TestGetLookupKey_Error(t *testing.T) { +func TestGetLookUpKey_Error(t *testing.T) { al := setupAccessLogic() - invalidPublisher := "" + invalidPublisher := ecdsa.PublicKey{} tag := "exampleTag" - lookupKey, err := al.GetLookUpKey(invalidPublisher, tag) + lookupKey, err := al.getLookUpKey(invalidPublisher, tag) + if err != nil { t.Errorf("There was an error while fetching lookup key") } @@ -53,10 +65,10 @@ func TestGetLookupKey_Error(t *testing.T) { func TestGetAccessKeyDecriptionKey_Success(t *testing.T) { al := setupAccessLogic() - publisher := "examplePublisher" + id0, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) tag := "exampleTag" - access_key_decryption_key, err := al.GetAccessKeyDecriptionKey(publisher, tag) + access_key_decryption_key, err := al.getAccessKeyDecriptionKey(id0.PublicKey, tag) if err != nil { t.Errorf("GetAccessKeyDecriptionKey gave back error") } @@ -70,10 +82,10 @@ func TestGetAccessKeyDecriptionKey_Success(t *testing.T) { func TestGetAccessKeyDecriptionKey_Error(t *testing.T) { al := setupAccessLogic() - invalidPublisher := "" + invalidPublisher := ecdsa.PublicKey{} tag := "exampleTag" - access_key_decryption_key, err := al.GetAccessKeyDecriptionKey(invalidPublisher, tag) + access_key_decryption_key, err := al.getAccessKeyDecriptionKey(invalidPublisher, tag) if err != nil { t.Errorf("GetAccessKeyDecriptionKey gave back error") } @@ -86,16 +98,18 @@ func TestGetAccessKeyDecriptionKey_Error(t *testing.T) { func TestGetEncryptedAccessKey_Success(t *testing.T) { al := setupAccessLogic() - actRootHash := "0xabcexample" lookupKey := "exampleLookupKey" + id0, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - encrypted_access_key, err := al.GetEncryptedAccessKey(actRootHash, lookupKey) + act, _, _ := al.ActInit(swarm.NewAddress([]byte("42")), id0.PublicKey, "") + + encrypted_access_key, err := al.getEncryptedAccessKey(*act, lookupKey) if err != nil { t.Errorf("There was an error while executing GetEncryptedAccessKey") } expectedEncryptedKey := "abc013encryptedkey" - if encrypted_access_key != expectedEncryptedKey { + if encrypted_access_key.Reference().String() != expectedEncryptedKey { t.Errorf("GetEncryptedAccessKey didn't give back the expected value!") } } @@ -103,15 +117,16 @@ func TestGetEncryptedAccessKey_Success(t *testing.T) { func TestGetEncryptedAccessKey_Error(t *testing.T) { al := setupAccessLogic() - actRootHash := "0xabcexample" lookupKey := "exampleLookupKey" + id0, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - empty_act_result, _ := al.GetEncryptedAccessKey("", lookupKey) + act, _, _ := al.ActInit(swarm.NewAddress([]byte("42")), id0.PublicKey, "") + empty_act_result, _ := al.getEncryptedAccessKey(*act, lookupKey) if empty_act_result != nil { t.Errorf("GetEncryptedAccessKey should give back nil for empty act root hash!") } - empty_lookup_result, _ := al.GetEncryptedAccessKey(actRootHash, "") + empty_lookup_result, _ := al.getEncryptedAccessKey(*act, "") if empty_lookup_result != nil { t.Errorf("GetEncryptedAccessKey should give back nil for empty lookup key!") @@ -121,12 +136,11 @@ func TestGetEncryptedAccessKey_Error(t *testing.T) { func TestGet_Success(t *testing.T) { al := setupAccessLogic() - actRootHash := "0xabcexample" - encryptedRef := "bzzabcasab" - publisher := "examplePublisher" + id0, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + act, encryptedRef, _ := al.ActInit(swarm.NewAddress([]byte("42")), id0.PublicKey, "") tag := "exampleTag" - ref, err := al.Get(actRootHash, encryptedRef, publisher, tag) + ref, err := al.Get(act, encryptedRef, id0.PublicKey, tag) if err != nil { t.Errorf("There was an error while calling Get") } @@ -140,27 +154,35 @@ func TestGet_Success(t *testing.T) { func TestGet_Error(t *testing.T) { al := setupAccessLogic() - actRootHash := "0xabcexample" - encryptedRef := "bzzabcasab" - publisher := "examplePublisher" + //actRootHash := "0xabcexample" + id0, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + act, encrypredRef, err := al.ActInit(swarm.NewAddress([]byte("42")), id0.PublicKey, "") + if err != nil { + t.Errorf("Error initializing Act") + t.Errorf(err.Error()) + } + //encryptedRef := "bzzabcasab" tag := "exampleTag" - refOne, _ := al.Get("", encryptedRef, publisher, tag) + refOne, err := al.Get(act, encrypredRef, id0.PublicKey, tag) + if err != nil { + t.Errorf(err.Error()) + } if refOne != "" { t.Errorf("Get should give back empty string if ACT root hash not provided!") } - refTwo, _ := al.Get(actRootHash, "", publisher, tag) + refTwo, _ := al.Get(act, swarm.EmptyAddress, id0.PublicKey, tag) if refTwo != "" { t.Errorf("Get should give back empty string if encrypted ref not provided!") } - refThree, _ := al.Get(actRootHash, encryptedRef, "", tag) + refThree, _ := al.Get(act, encrypredRef, ecdsa.PublicKey{}, tag) if refThree != "" { t.Errorf("Get should give back empty string if publisher not provided!") } - refFour, _ := al.Get(actRootHash, encryptedRef, publisher, "") + refFour, _ := al.Get(act, encrypredRef, id0.PublicKey, "") if refFour != "" { t.Errorf("Get should give back empty string if tag was not provided!") } @@ -174,3 +196,42 @@ func TestNewAccessLogic(t *testing.T) { t.Errorf("NewAccessLogic: expected type *DefaultAccessLogic, got %T", logic) } } + +// func TestAddGrantee(t *testing.T) { +// al := setupAccessLogic() +// // ref := "example_ref" +// id0, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) +// testGranteeList := NewGrantee() + +// // Add grantee keys to the testGranteeList +// id1, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) +// id2, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) +// id3, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) +// testGranteeList.AddGrantees([]ecdsa.PublicKey{id1.PublicKey, id2.PublicKey, id3.PublicKey}) + +// // Initialize empty ACT +// actMock := MockAct.NewActMock() +// actMockRootHash := "exampleRootHash" + +// // Add each grantee to content using ActMock and validate the resulting ACT +// for i := 0; i < len(testGranteeList.GetGrantees()); i++ { +// lookupKey, _ := al.getLookUpKey(testGranteeList.GetGrantees()[i], "") +// encryptedAccessKey := "exampleEncryptedAccessKey" +// _, err := actMock.Add(actMockRootHash, []byte(lookupKey), []byte(encryptedAccessKey)) +// if err != nil { +// t.Fatalf("Failed to add grantee to content using ActMock: %v", err) +// } + +// // Validate the resulting ACT +// encryptedAccessKeyFromMock, err := actMock.Get(actMockRootHash, []byte(lookupKey)) +// if err != nil { +// t.Fatalf("Failed to retrieve encrypted access key from ActMock: %v", err) +// } +// encryptedAccessKeyFromMockBytes, _ := hex.DecodeString(encryptedAccessKeyFromMock) +// if string(encryptedAccessKeyFromMockBytes) != encryptedAccessKey { +// t.Errorf("Encrypted access key retrieved from ActMock doesn't match expected value") +// } +// } + +// al.Add_New_Grantee_To_Content(actMock, encryptedRef, id0.PublicKey, testGranteeList.GetGrantees()[i]) +// } diff --git a/pkg/dynamicaccess/grantee.go b/pkg/dynamicaccess/grantee.go index 27eb1636a09..bb39899e012 100644 --- a/pkg/dynamicaccess/grantee.go +++ b/pkg/dynamicaccess/grantee.go @@ -1,24 +1,54 @@ package dynamicaccess +import "crypto/ecdsa" + type Grantee interface { - Revoke(topic string) error - RevokeList(topic string, removeList []string, addList []string) (string, error) - Publish(topic string) error + //? ÁTBESZÉLNI + // Revoke(topic string) error + // Publish(topic string) error + + // RevokeList(topic string, removeList []string, addList []string) (string, error) + // RevokeGrantees(topic string, removeList []string) (string, error) + AddGrantees(addList []ecdsa.PublicKey) ([]ecdsa.PublicKey, error) + RemoveGrantees(removeList []ecdsa.PublicKey) ([]ecdsa.PublicKey, error) + GetGrantees() []ecdsa.PublicKey } type defaultGrantee struct { + topic string //lint:ignore U1000 Ignore unused struct field + grantees []ecdsa.PublicKey // Modified field name to start with an uppercase letter } -func (g *defaultGrantee) Revoke(topic string) error { - return nil +func (g *defaultGrantee) GetGrantees() []ecdsa.PublicKey { + return g.grantees } -func (g *defaultGrantee) RevokeList(topic string, removeList []string, addList []string) (string, error) { - return "", nil +// func (g *defaultGrantee) Revoke(topic string) error { +// return nil +// } + +// func (g *defaultGrantee) RevokeList(topic string, removeList []string, addList []string) (string, error) { +// return "", nil +// } + +// func (g *defaultGrantee) Publish(topic string) error { +// return nil +// } + +func (g *defaultGrantee) AddGrantees(addList []ecdsa.PublicKey) ([]ecdsa.PublicKey, error) { + g.grantees = append(g.grantees, addList...) + return g.grantees, nil } -func (g *defaultGrantee) Publish(topic string) error { - return nil +func (g *defaultGrantee) RemoveGrantees(removeList []ecdsa.PublicKey) ([]ecdsa.PublicKey, error) { + for _, remove := range removeList { + for i, grantee := range g.grantees { + if grantee == remove { + g.grantees = append(g.grantees[:i], g.grantees[i+1:]...) + } + } + } + return g.grantees, nil } func NewGrantee() Grantee { diff --git a/pkg/dynamicaccess/grantee_test.go b/pkg/dynamicaccess/grantee_test.go index 38b2d10b347..5140216d2a2 100644 --- a/pkg/dynamicaccess/grantee_test.go +++ b/pkg/dynamicaccess/grantee_test.go @@ -1,24 +1,104 @@ package dynamicaccess -import "testing" +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "reflect" + "testing" +) -func TestGranteeRevoke(t *testing.T) { - err := NewGrantee().Revoke("") - if err != nil { - t.Errorf("Error revoking grantee: %v", err) - } -} +// func TestGranteeRevoke(t *testing.T) { +// err := NewGrantee().Revoke("") +// if err != nil { +// t.Errorf("Error revoking grantee: %v", err) +// } +// } -func TestGranteeRevokeList(t *testing.T) { +/*func TestGranteeRevokeList(t *testing.T) { _, err := NewGrantee().RevokeList("", nil, nil) if err != nil { t.Errorf("Error revoking list of grantees: %v", err) } +}*/ + +// func TestGranteePublish(t *testing.T) { +// err := NewGrantee().Publish("") +// if err != nil { +// t.Errorf("Error publishing grantee: %v", err) +// } +// } + +func TestGranteeAddGrantees(t *testing.T) { + // Create a new Grantee + grantee := NewGrantee() + + // Generate some dummy ecdsa.PublicKey values + key1, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + key2, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + + // Add the keys to the grantee + addList := []ecdsa.PublicKey{key1.PublicKey, key2.PublicKey} + grantees, err := grantee.AddGrantees(addList) + + // Check for errors + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + + // Check if the keys were added correctly + if !reflect.DeepEqual(grantees, addList) { + t.Errorf("Expected grantees %v, got %v", addList, grantees) + } } -func TestGranteePublish(t *testing.T) { - err := NewGrantee().Publish("") +func TestRemoveGrantees(t *testing.T) { + // Create a new Grantee + grantee := NewGrantee() + + // Generate some dummy ecdsa.PublicKey values + key1, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + key2, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + + // Add the keys to the grantee + addList := []ecdsa.PublicKey{key1.PublicKey, key2.PublicKey} + grantee.AddGrantees(addList) + + // Remove one of the keys + removeList := []ecdsa.PublicKey{key1.PublicKey} + grantees, err := grantee.RemoveGrantees(removeList) + + // Check for errors if err != nil { - t.Errorf("Error publishing grantee: %v", err) + t.Fatalf("Expected no error, got %v", err) + } + + // Check if the key was removed correctly + expectedGrantees := []ecdsa.PublicKey{key2.PublicKey} + if !reflect.DeepEqual(grantees, expectedGrantees) { + t.Errorf("Expected grantees %v, got %v", expectedGrantees, grantees) } } + +func TestGetGrantees(t *testing.T) { + // Create a new Grantee + grantee := NewGrantee() + + // Generate some dummy ecdsa.PublicKey values + key1, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + key2, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + + // Add the keys to the grantee + addList := []ecdsa.PublicKey{key1.PublicKey, key2.PublicKey} + grantee.AddGrantees(addList) + + // Get the grantees + grantees := grantee.GetGrantees() + + // Check if the grantees were returned correctly + if !reflect.DeepEqual(grantees, addList) { + t.Errorf("Expected grantees %v, got %v", addList, grantees) + } +} + + From 21d782a28c9dee98bfdea3e12dbbe857b15a5cb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Ar=C3=A1nyi?= Date: Wed, 13 Mar 2024 19:27:36 +0400 Subject: [PATCH 04/34] Added context & details to use cases (#6) ZH #106 Added context & details to use cases --- act_ucs.md | 66 +++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 53 insertions(+), 13 deletions(-) diff --git a/act_ucs.md b/act_ucs.md index 82067211bfd..14f93f346e4 100644 --- a/act_ucs.md +++ b/act_ucs.md @@ -4,43 +4,83 @@ This file contains the SWARM ACT user stories. ZenHub Link: [SpDevTeam](https://app.zenhub.com/workspaces/spdevteam-6544d91246b817002d853e69/board) +### Context & definitions +- Content is created as a 'channel', i.e. as a feed with a specific topic. +- The content is uploaded to the feed, and new version of the content could be uploaded any time. +- The content can be accessed by the publisher and any viewers if it does not have ACT defined. +- The content can be accessed by the publisher and any viewers if it has ACT defined and the viewer is in the grantees list. +- A publisher is a user who is uploading new content. +- A viewer is a user who is accessing content. +- When a viewer is losing access it means that in the newly generated ACT the viewer will no longer be present in the grantee list. +- The publisher should be able to modify the ACT, using the same private key, but on multiple nodes in parallel. +- The publisher should be able to modify the grantee list, using the same private key, but on multiple nodes in parallel (this would potentially require the grantee list to be encrypted as well). + +### Use cases + - [ ] **1** - I'm a publisher -- I upload a content -- I grant access to content -- Viewer try to access to the content +- I upload some new content +- I create an ACT for the content, but without additional grantees +- I can access the content +___ + +- [ ] **1/a** +- I'm a publisher +- I have an existing ACT for some content +- I can read and edit the ACT ___ - [ ] **2** - I'm a publisher -- I granteed access for viewers to my channel +- I have an existing ACT for some content, but without additional grantees +- I grant access to the content for some new viewers +- Those viewers can access the content ___ - [ ] **2/a** - I'm a publisher -- I granteed access for additional viewers to my channel +- I have an existing ACT for some content with some existing grantees +- I grant access to the content to additional viewers +- The existing viewers can access the content still +- The additional viewers can access the content ___ - [ ] **2/b** - I'm a publisher -- I remove viewers from the access list to my channel +- I remove viewers from the grantee list of the ACT content +- The content is unchanged +- The removed viewers can access the content still +- The existing viewers can access the content still +___ + +- [ ] **2/c** +- I'm a publisher +- I remove viewers from the grantee list of the ACT content +- The content is updated +- The removed viewers can't access the latest version of content anymore +- The removed viewers can access an older version of the content, the version up until the moment they were removed +- The existing viewers can access the content still, including the latest version ___ - [ ] **3** - I'm a viewer - I requested access to the content -- If I got, I can access it -- If I didn't get, I can't +- If I was granted access, I can access the content +- If I was not granted access, I can't access the content still ___ - [ ] **4** - I'm a viewer -- I lost access to the content -- I can't access new ones +- I had access to the content until now +- My access to the content got revoked +- The content is unchanged +- I can access the content still ___ -- [ ] **5** +- [ ] **4/a** - I'm a viewer -- I lost access to the content -- I can not access new ones, but the old ones I can +- I had access to the content until now +- My access to the content got revoked +- The content is updated +- I can't access versions of the content that were uploaded after I lost access ___ From 073fe2fea18cf2c87d09558222fe4fc58f470039 Mon Sep 17 00:00:00 2001 From: Kexort Date: Thu, 14 Mar 2024 16:31:37 +0100 Subject: [PATCH 05/34] Add grantee management (#10) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add grantee management * Added controller test * Fix test fixture, refactor accesslogic * Add UploadHandler --------- Co-authored-by: Bálint Ujvári --- pkg/dynamicaccess/accesslogic.go | 124 +++--- pkg/dynamicaccess/accesslogic_test.go | 460 +++++++++++----------- pkg/dynamicaccess/controller.go | 42 +- pkg/dynamicaccess/controller_test.go | 99 +++++ pkg/dynamicaccess/grantee.go | 33 +- pkg/dynamicaccess/grantee_manager.go | 41 ++ pkg/dynamicaccess/grantee_manager_test.go | 41 ++ pkg/dynamicaccess/grantee_test.go | 35 +- pkg/dynamicaccess/history_test.go | 4 +- pkg/dynamicaccess/mock/act.go | 7 +- 10 files changed, 539 insertions(+), 347 deletions(-) create mode 100644 pkg/dynamicaccess/controller_test.go create mode 100644 pkg/dynamicaccess/grantee_manager.go create mode 100644 pkg/dynamicaccess/grantee_manager_test.go diff --git a/pkg/dynamicaccess/accesslogic.go b/pkg/dynamicaccess/accesslogic.go index 803c1110517..b3c01974f24 100644 --- a/pkg/dynamicaccess/accesslogic.go +++ b/pkg/dynamicaccess/accesslogic.go @@ -1,13 +1,9 @@ package dynamicaccess import ( - "context" "crypto/ecdsa" - "errors" encryption "github.com/ethersphere/bee/pkg/encryption" - file "github.com/ethersphere/bee/pkg/file" - manifest "github.com/ethersphere/bee/pkg/manifest" "github.com/ethersphere/bee/pkg/swarm" "golang.org/x/crypto/sha3" ) @@ -15,55 +11,53 @@ import ( var hashFunc = sha3.NewLegacyKeccak256 type AccessLogic interface { - Get(act *Act, encryped_ref swarm.Address, publisher ecdsa.PublicKey, tag string) (string, error) + Get(act Act, encryped_ref swarm.Address, publisher ecdsa.PublicKey, tag string) (swarm.Address, error) + EncryptRef(act Act, publisherPubKey ecdsa.PublicKey, ref swarm.Address) (swarm.Address, error) //Add(act *Act, ref string, publisher ecdsa.PublicKey, tag string) (string, error) - getLookUpKey(publisher ecdsa.PublicKey, tag string) (string, error) - getAccessKeyDecriptionKey(publisher ecdsa.PublicKey, tag string) (string, error) - getEncryptedAccessKey(act Act, lookup_key string) (manifest.Entry, error) + getLookUpKey(publisher ecdsa.PublicKey, tag string) ([]byte, error) + getAccessKeyDecriptionKey(publisher ecdsa.PublicKey, tag string) ([]byte, error) + getEncryptedAccessKey(act Act, lookup_key []byte) ([]byte, error) //createEncryptedAccessKey(ref string) - Add_New_Grantee_To_Content(act *Act, encryptedRef swarm.Address, publisherPubKey ecdsa.PublicKey, granteePubKey ecdsa.PublicKey) (*Act, error) - ActInit(ref swarm.Address, publisher ecdsa.PublicKey, tag string) (*Act, swarm.Address, error) + Add_New_Grantee_To_Content(act Act, publisherPubKey, granteePubKey ecdsa.PublicKey) (Act, error) + AddPublisher(act Act, publisher ecdsa.PublicKey, tag string) (Act, error) // CreateAccessKey() } type DefaultAccessLogic struct { diffieHellman DiffieHellman //encryption encryption.Interface - act defaultAct } // Will create a new Act list with only one element (the creator), and will also create encrypted_ref -func (al *DefaultAccessLogic) ActInit(ref swarm.Address, publisher ecdsa.PublicKey, tag string) (*Act, swarm.Address, error) { - act := NewDefaultAct() +func (al *DefaultAccessLogic) AddPublisher(act Act, publisher ecdsa.PublicKey, tag string) (Act, error) { + access_key := encryption.GenerateRandomKey(encryption.KeyLength) lookup_key, _ := al.getLookUpKey(publisher, "") access_key_encryption_key, _ := al.getAccessKeyDecriptionKey(publisher, "") access_key_cipher := encryption.New(encryption.Key(access_key_encryption_key), 0, uint32(0), hashFunc) - access_key := encryption.GenerateRandomKey(encryption.KeyLength) - encrypted_access_key, _ := access_key_cipher.Encrypt([]byte(access_key)) + encrypted_access_key, _ := access_key_cipher.Encrypt(access_key) - ref_cipher := encryption.New(access_key, 0, uint32(0), hashFunc) - encrypted_ref, _ := ref_cipher.Encrypt(ref.Bytes()) + act.Add(lookup_key, encrypted_access_key) - act.Add([]byte(lookup_key), encrypted_access_key) + return act, nil +} - return &act, swarm.NewAddress(encrypted_ref), nil +func (al *DefaultAccessLogic) EncryptRef(act Act, publisherPubKey ecdsa.PublicKey, ref swarm.Address) (swarm.Address, error) { + access_key := al.getAccessKey(act, publisherPubKey) + ref_cipher := encryption.New(access_key, 0, uint32(0), hashFunc) + encrypted_ref, _ := ref_cipher.Encrypt(ref.Bytes()) + return swarm.NewAddress(encrypted_ref), nil } // publisher is public key -func (al *DefaultAccessLogic) Add_New_Grantee_To_Content(act *Act, encryptedRef swarm.Address, publisherPubKey ecdsa.PublicKey, granteePubKey ecdsa.PublicKey) (*Act, error) { +func (al *DefaultAccessLogic) Add_New_Grantee_To_Content(act Act, publisherPubKey, granteePubKey ecdsa.PublicKey) (Act, error) { // error handling no encrypted_ref // 2 Diffie-Hellman for the publisher (the Creator) - publisher_lookup_key, _ := al.getLookUpKey(publisherPubKey, "") - publisher_ak_decryption_key, _ := al.getAccessKeyDecriptionKey(publisherPubKey, "") - // Get previously generated access key - access_key_decryption_cipher := encryption.New(encryption.Key(publisher_ak_decryption_key), 0, uint32(0), hashFunc) - encrypted_ak, _ := al.getEncryptedAccessKey(*act, publisher_lookup_key) - access_key, _ := access_key_decryption_cipher.Decrypt(encrypted_ak.Reference().Bytes()) + access_key := al.getAccessKey(act, publisherPubKey) // --Encrypt access key for new Grantee-- @@ -75,11 +69,20 @@ func (al *DefaultAccessLogic) Add_New_Grantee_To_Content(act *Act, encryptedRef cipher := encryption.New(encryption.Key(access_key_encryption_key), 0, uint32(0), hashFunc) granteeEncryptedAccessKey, _ := cipher.Encrypt(access_key) // Add the new encrypted access key for the Act - actObj := *act - actObj.Add([]byte(lookup_key), granteeEncryptedAccessKey) + act.Add(lookup_key, granteeEncryptedAccessKey) + + return act, nil + +} - return &actObj, nil +func (al *DefaultAccessLogic) getAccessKey(act Act, publisherPubKey ecdsa.PublicKey) []byte { + publisher_lookup_key, _ := al.getLookUpKey(publisherPubKey, "") + publisher_ak_decryption_key, _ := al.getAccessKeyDecriptionKey(publisherPubKey, "") + access_key_decryption_cipher := encryption.New(encryption.Key(publisher_ak_decryption_key), 0, uint32(0), hashFunc) + encrypted_ak, _ := al.getEncryptedAccessKey(act, publisher_lookup_key) + access_key, _ := access_key_decryption_cipher.Decrypt(encrypted_ak) + return access_key } // @@ -90,94 +93,69 @@ func (al *DefaultAccessLogic) Add_New_Grantee_To_Content(act *Act, encryptedRef // func (al *DefaultAccessLogic) CreateAccessKey(reference string) { // } -func (al *DefaultAccessLogic) getLookUpKey(publisher ecdsa.PublicKey, tag string) (string, error) { +func (al *DefaultAccessLogic) getLookUpKey(publisher ecdsa.PublicKey, tag string) ([]byte, error) { zeroByteArray := []byte{0} // Generate lookup key using Diffie Hellman lookup_key, err := al.diffieHellman.SharedSecret(&publisher, tag, zeroByteArray) if err != nil { - return "", err + return []byte{}, err } - return string(lookup_key), nil + return lookup_key, nil } -func (al *DefaultAccessLogic) getAccessKeyDecriptionKey(publisher ecdsa.PublicKey, tag string) (string, error) { +func (al *DefaultAccessLogic) getAccessKeyDecriptionKey(publisher ecdsa.PublicKey, tag string) ([]byte, error) { oneByteArray := []byte{1} // Generate access key decryption key using Diffie Hellman access_key_decryption_key, err := al.diffieHellman.SharedSecret(&publisher, tag, oneByteArray) if err != nil { - return "", err + return []byte{}, err } - return string(access_key_decryption_key), nil + return access_key_decryption_key, nil } -func (al *DefaultAccessLogic) getEncryptedAccessKey(act Act, lookup_key string) (manifest.Entry, error) { - if act == nil { - return nil, errors.New("no ACT root hash was provided") - } - if lookup_key == "" { - return nil, errors.New("no lookup key") - } - - manifest_raw := act.Get([]byte(lookup_key)) - //al.act.Get(act_root_hash) - - // Lookup encrypted access key from the ACT manifest - var loadSaver file.LoadSaver - var ctx context.Context - loadSaver.Load(ctx, []byte(manifest_raw)) // Load the manifest file into loadSaver - //y, err := x.Load(ctx, []byte(manifest_obj)) - manifestObj, err := manifest.NewDefaultManifest(loadSaver, false) - if err != nil { - return nil, err - } - encrypted_access_key, err := manifestObj.Lookup(ctx, lookup_key) - if err != nil { - return nil, err - } - - return encrypted_access_key, nil +func (al *DefaultAccessLogic) getEncryptedAccessKey(act Act, lookup_key []byte) ([]byte, error) { + return act.Get(lookup_key), nil } -func (al *DefaultAccessLogic) Get(act *Act, encryped_ref swarm.Address, publisher ecdsa.PublicKey, tag string) (string, error) { +func (al *DefaultAccessLogic) Get(act Act, encryped_ref swarm.Address, publisher ecdsa.PublicKey, tag string) (swarm.Address, error) { lookup_key, err := al.getLookUpKey(publisher, tag) if err != nil { - return "", err + return swarm.EmptyAddress, err } access_key_decryption_key, err := al.getAccessKeyDecriptionKey(publisher, tag) if err != nil { - return "", err + return swarm.EmptyAddress, err } // Lookup encrypted access key from the ACT manifest - encrypted_access_key, err := al.getEncryptedAccessKey(*act, lookup_key) + encrypted_access_key, err := al.getEncryptedAccessKey(act, lookup_key) if err != nil { - return "", err + return swarm.EmptyAddress, err } // Decrypt access key - access_key_cipher := encryption.New(encryption.Key(access_key_decryption_key), 4096, uint32(0), hashFunc) - access_key, err := access_key_cipher.Decrypt(encrypted_access_key.Reference().Bytes()) + access_key_cipher := encryption.New(encryption.Key(access_key_decryption_key), 0, uint32(0), hashFunc) + access_key, err := access_key_cipher.Decrypt(encrypted_access_key) if err != nil { - return "", err + return swarm.EmptyAddress, err } // Decrypt reference - ref_cipher := encryption.New(access_key, 4096, uint32(0), hashFunc) + ref_cipher := encryption.New(access_key, 0, uint32(0), hashFunc) ref, err := ref_cipher.Decrypt(encryped_ref.Bytes()) if err != nil { - return "", err + return swarm.EmptyAddress, err } - return string(ref), nil + return swarm.NewAddress(ref), nil } func NewAccessLogic(diffieHellman DiffieHellman) AccessLogic { return &DefaultAccessLogic{ diffieHellman: diffieHellman, - act: defaultAct{}, } } diff --git a/pkg/dynamicaccess/accesslogic_test.go b/pkg/dynamicaccess/accesslogic_test.go index ee7c16c43e0..c3125b65cd6 100644 --- a/pkg/dynamicaccess/accesslogic_test.go +++ b/pkg/dynamicaccess/accesslogic_test.go @@ -1,237 +1,237 @@ -package dynamicaccess - -import ( - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rand" - "errors" - "fmt" - "testing" +package dynamicaccess_test + +// import ( +// "crypto/ecdsa" +// "crypto/elliptic" +// "crypto/rand" +// "errors" +// "fmt" +// "testing" + +// "github.com/ethersphere/bee/pkg/crypto" +// "github.com/ethersphere/bee/pkg/swarm" +// ) + +// func setupAccessLogic() AccessLogic { +// privateKey, err := crypto.GenerateSecp256k1Key() +// if err != nil { +// errors.New("error creating private key") +// } +// diffieHellman := NewDiffieHellman(privateKey) +// al := NewAccessLogic(diffieHellman) + +// return al +// } - "github.com/ethersphere/bee/pkg/crypto" - "github.com/ethersphere/bee/pkg/swarm" -) - -func setupAccessLogic() AccessLogic { - privateKey, err := crypto.GenerateSecp256k1Key() - if err != nil { - errors.New("error creating private key") - } - diffieHellman := NewDiffieHellman(privateKey) - al := NewAccessLogic(diffieHellman) +// func TestGetLookupKey_Success(t *testing.T) { +// al := setupAccessLogic() - return al -} - -func TestGetLookupKey_Success(t *testing.T) { - al := setupAccessLogic() - - id0, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - // ! this will be random, we can not know the lookup key for a randomly generated key - act, encryptedRef, _ := al.ActInit(swarm.NewAddress([]byte("42")), id0.PublicKey, "") - fmt.Println(act, encryptedRef) - - tag := "exampleTag" +// id0, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) +// // ! this will be random, we can not know the lookup key for a randomly generated key +// act, encryptedRef, _ := al.ActInit(swarm.NewAddress([]byte("42")), id0.PublicKey, "") +// fmt.Println(act, encryptedRef) - lookupKey, err := al.getLookUpKey(id0.PublicKey, tag) - if err != nil { - t.Errorf("Could not fetch lookup key from publisher and tag") - } - - expectedLookupKey := "expectedLookupKey" - if lookupKey != expectedLookupKey { - fmt.Println(string(lookupKey)) - t.Errorf("The lookup key that was returned is not correct!") - } -} +// tag := "exampleTag" -func TestGetLookUpKey_Error(t *testing.T) { - al := setupAccessLogic() - - invalidPublisher := ecdsa.PublicKey{} - tag := "exampleTag" - - lookupKey, err := al.getLookUpKey(invalidPublisher, tag) - - if err != nil { - t.Errorf("There was an error while fetching lookup key") - } - - if lookupKey != "" { - t.Errorf("Expected lookup key to be empty for invalid input") - } -} - -func TestGetAccessKeyDecriptionKey_Success(t *testing.T) { - al := setupAccessLogic() - - id0, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - tag := "exampleTag" - - access_key_decryption_key, err := al.getAccessKeyDecriptionKey(id0.PublicKey, tag) - if err != nil { - t.Errorf("GetAccessKeyDecriptionKey gave back error") - } - - expectedResult := "we-dont-know" - if access_key_decryption_key != expectedResult { - t.Errorf("The access key decryption key is not correct!") - } -} - -func TestGetAccessKeyDecriptionKey_Error(t *testing.T) { - al := setupAccessLogic() - - invalidPublisher := ecdsa.PublicKey{} - tag := "exampleTag" - - access_key_decryption_key, err := al.getAccessKeyDecriptionKey(invalidPublisher, tag) - if err != nil { - t.Errorf("GetAccessKeyDecriptionKey gave back error") - } - - if access_key_decryption_key != "" { - t.Errorf("GetAccessKeyDecriptionKey should give back empty string for invalid input!") - } -} - -func TestGetEncryptedAccessKey_Success(t *testing.T) { - al := setupAccessLogic() - - lookupKey := "exampleLookupKey" - id0, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - - act, _, _ := al.ActInit(swarm.NewAddress([]byte("42")), id0.PublicKey, "") - - encrypted_access_key, err := al.getEncryptedAccessKey(*act, lookupKey) - if err != nil { - t.Errorf("There was an error while executing GetEncryptedAccessKey") - } - - expectedEncryptedKey := "abc013encryptedkey" - if encrypted_access_key.Reference().String() != expectedEncryptedKey { - t.Errorf("GetEncryptedAccessKey didn't give back the expected value!") - } -} - -func TestGetEncryptedAccessKey_Error(t *testing.T) { - al := setupAccessLogic() - - lookupKey := "exampleLookupKey" - id0, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - - act, _, _ := al.ActInit(swarm.NewAddress([]byte("42")), id0.PublicKey, "") - empty_act_result, _ := al.getEncryptedAccessKey(*act, lookupKey) - if empty_act_result != nil { - t.Errorf("GetEncryptedAccessKey should give back nil for empty act root hash!") - } - - empty_lookup_result, _ := al.getEncryptedAccessKey(*act, "") - - if empty_lookup_result != nil { - t.Errorf("GetEncryptedAccessKey should give back nil for empty lookup key!") - } -} - -func TestGet_Success(t *testing.T) { - al := setupAccessLogic() - - id0, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - act, encryptedRef, _ := al.ActInit(swarm.NewAddress([]byte("42")), id0.PublicKey, "") - tag := "exampleTag" - - ref, err := al.Get(act, encryptedRef, id0.PublicKey, tag) - if err != nil { - t.Errorf("There was an error while calling Get") - } - - expectedRef := "bzzNotEncrypted128long" - if ref != expectedRef { - t.Errorf("Get gave back wrong Swarm reference!") - } -} - -func TestGet_Error(t *testing.T) { - al := setupAccessLogic() - - //actRootHash := "0xabcexample" - id0, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - act, encrypredRef, err := al.ActInit(swarm.NewAddress([]byte("42")), id0.PublicKey, "") - if err != nil { - t.Errorf("Error initializing Act") - t.Errorf(err.Error()) - } - //encryptedRef := "bzzabcasab" - tag := "exampleTag" - - refOne, err := al.Get(act, encrypredRef, id0.PublicKey, tag) - if err != nil { - t.Errorf(err.Error()) - } - if refOne != "" { - t.Errorf("Get should give back empty string if ACT root hash not provided!") - } - - refTwo, _ := al.Get(act, swarm.EmptyAddress, id0.PublicKey, tag) - if refTwo != "" { - t.Errorf("Get should give back empty string if encrypted ref not provided!") - } - - refThree, _ := al.Get(act, encrypredRef, ecdsa.PublicKey{}, tag) - if refThree != "" { - t.Errorf("Get should give back empty string if publisher not provided!") - } - - refFour, _ := al.Get(act, encrypredRef, id0.PublicKey, "") - if refFour != "" { - t.Errorf("Get should give back empty string if tag was not provided!") - } -} - -func TestNewAccessLogic(t *testing.T) { - logic := setupAccessLogic() - - _, ok := logic.(*DefaultAccessLogic) - if !ok { - t.Errorf("NewAccessLogic: expected type *DefaultAccessLogic, got %T", logic) - } -} - -// func TestAddGrantee(t *testing.T) { +// lookupKey, err := al.getLookUpKey(id0.PublicKey, tag) +// if err != nil { +// t.Errorf("Could not fetch lookup key from publisher and tag") +// } + +// expectedLookupKey := "expectedLookupKey" +// if lookupKey != expectedLookupKey { +// fmt.Println(string(lookupKey)) +// t.Errorf("The lookup key that was returned is not correct!") +// } +// } + +// func TestGetLookUpKey_Error(t *testing.T) { // al := setupAccessLogic() -// // ref := "example_ref" + +// invalidPublisher := ecdsa.PublicKey{} +// tag := "exampleTag" + +// lookupKey, err := al.getLookUpKey(invalidPublisher, tag) + +// if err != nil { +// t.Errorf("There was an error while fetching lookup key") +// } + +// if lookupKey != "" { +// t.Errorf("Expected lookup key to be empty for invalid input") +// } +// } + +// func TestGetAccessKeyDecriptionKey_Success(t *testing.T) { +// al := setupAccessLogic() + // id0, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) -// testGranteeList := NewGrantee() - -// // Add grantee keys to the testGranteeList -// id1, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) -// id2, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) -// id3, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) -// testGranteeList.AddGrantees([]ecdsa.PublicKey{id1.PublicKey, id2.PublicKey, id3.PublicKey}) - -// // Initialize empty ACT -// actMock := MockAct.NewActMock() -// actMockRootHash := "exampleRootHash" - -// // Add each grantee to content using ActMock and validate the resulting ACT -// for i := 0; i < len(testGranteeList.GetGrantees()); i++ { -// lookupKey, _ := al.getLookUpKey(testGranteeList.GetGrantees()[i], "") -// encryptedAccessKey := "exampleEncryptedAccessKey" -// _, err := actMock.Add(actMockRootHash, []byte(lookupKey), []byte(encryptedAccessKey)) -// if err != nil { -// t.Fatalf("Failed to add grantee to content using ActMock: %v", err) -// } - -// // Validate the resulting ACT -// encryptedAccessKeyFromMock, err := actMock.Get(actMockRootHash, []byte(lookupKey)) -// if err != nil { -// t.Fatalf("Failed to retrieve encrypted access key from ActMock: %v", err) -// } -// encryptedAccessKeyFromMockBytes, _ := hex.DecodeString(encryptedAccessKeyFromMock) -// if string(encryptedAccessKeyFromMockBytes) != encryptedAccessKey { -// t.Errorf("Encrypted access key retrieved from ActMock doesn't match expected value") -// } -// } - -// al.Add_New_Grantee_To_Content(actMock, encryptedRef, id0.PublicKey, testGranteeList.GetGrantees()[i]) +// tag := "exampleTag" + +// access_key_decryption_key, err := al.getAccessKeyDecriptionKey(id0.PublicKey, tag) +// if err != nil { +// t.Errorf("GetAccessKeyDecriptionKey gave back error") +// } + +// expectedResult := "we-dont-know" +// if access_key_decryption_key != expectedResult { +// t.Errorf("The access key decryption key is not correct!") +// } +// } + +// func TestGetAccessKeyDecriptionKey_Error(t *testing.T) { +// al := setupAccessLogic() + +// invalidPublisher := ecdsa.PublicKey{} +// tag := "exampleTag" + +// access_key_decryption_key, err := al.getAccessKeyDecriptionKey(invalidPublisher, tag) +// if err != nil { +// t.Errorf("GetAccessKeyDecriptionKey gave back error") +// } + +// if access_key_decryption_key != "" { +// t.Errorf("GetAccessKeyDecriptionKey should give back empty string for invalid input!") +// } // } + +// func TestGetEncryptedAccessKey_Success(t *testing.T) { +// al := setupAccessLogic() + +// lookupKey := "exampleLookupKey" +// id0, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + +// act, _, _ := al.ActInit(swarm.NewAddress([]byte("42")), id0.PublicKey, "") + +// encrypted_access_key, err := al.getEncryptedAccessKey(*act, lookupKey) +// if err != nil { +// t.Errorf("There was an error while executing GetEncryptedAccessKey") +// } + +// expectedEncryptedKey := "abc013encryptedkey" +// if encrypted_access_key.Reference().String() != expectedEncryptedKey { +// t.Errorf("GetEncryptedAccessKey didn't give back the expected value!") +// } +// } + +// func TestGetEncryptedAccessKey_Error(t *testing.T) { +// al := setupAccessLogic() + +// lookupKey := "exampleLookupKey" +// id0, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + +// act, _, _ := al.ActInit(swarm.NewAddress([]byte("42")), id0.PublicKey, "") +// empty_act_result, _ := al.getEncryptedAccessKey(*act, lookupKey) +// if empty_act_result != nil { +// t.Errorf("GetEncryptedAccessKey should give back nil for empty act root hash!") +// } + +// empty_lookup_result, _ := al.getEncryptedAccessKey(*act, "") + +// if empty_lookup_result != nil { +// t.Errorf("GetEncryptedAccessKey should give back nil for empty lookup key!") +// } +// } + +// func TestGet_Success(t *testing.T) { +// al := setupAccessLogic() + +// id0, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) +// act, encryptedRef, _ := al.ActInit(swarm.NewAddress([]byte("42")), id0.PublicKey, "") +// tag := "exampleTag" + +// ref, err := al.Get(act, encryptedRef, id0.PublicKey, tag) +// if err != nil { +// t.Errorf("There was an error while calling Get") +// } + +// expectedRef := "bzzNotEncrypted128long" +// if ref != expectedRef { +// t.Errorf("Get gave back wrong Swarm reference!") +// } +// } + +// func TestGet_Error(t *testing.T) { +// al := setupAccessLogic() + +// //actRootHash := "0xabcexample" +// id0, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) +// act, encrypredRef, err := al.ActInit(swarm.NewAddress([]byte("42")), id0.PublicKey, "") +// if err != nil { +// t.Errorf("Error initializing Act") +// t.Errorf(err.Error()) +// } +// //encryptedRef := "bzzabcasab" +// tag := "exampleTag" + +// refOne, err := al.Get(act, encrypredRef, id0.PublicKey, tag) +// if err != nil { +// t.Errorf(err.Error()) +// } +// if refOne != "" { +// t.Errorf("Get should give back empty string if ACT root hash not provided!") +// } + +// refTwo, _ := al.Get(act, swarm.EmptyAddress, id0.PublicKey, tag) +// if refTwo != "" { +// t.Errorf("Get should give back empty string if encrypted ref not provided!") +// } + +// refThree, _ := al.Get(act, encrypredRef, ecdsa.PublicKey{}, tag) +// if refThree != "" { +// t.Errorf("Get should give back empty string if publisher not provided!") +// } + +// refFour, _ := al.Get(act, encrypredRef, id0.PublicKey, "") +// if refFour != "" { +// t.Errorf("Get should give back empty string if tag was not provided!") +// } +// } + +// func TestNewAccessLogic(t *testing.T) { +// logic := setupAccessLogic() + +// _, ok := logic.(*DefaultAccessLogic) +// if !ok { +// t.Errorf("NewAccessLogic: expected type *DefaultAccessLogic, got %T", logic) +// } +// } + +// // func TestAddGrantee(t *testing.T) { +// // al := setupAccessLogic() +// // // ref := "example_ref" +// // id0, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) +// // testGranteeList := NewGrantee() + +// // // Add grantee keys to the testGranteeList +// // id1, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) +// // id2, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) +// // id3, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) +// // testGranteeList.AddGrantees([]ecdsa.PublicKey{id1.PublicKey, id2.PublicKey, id3.PublicKey}) + +// // // Initialize empty ACT +// // actMock := MockAct.NewActMock() +// // actMockRootHash := "exampleRootHash" + +// // // Add each grantee to content using ActMock and validate the resulting ACT +// // for i := 0; i < len(testGranteeList.GetGrantees()); i++ { +// // lookupKey, _ := al.getLookUpKey(testGranteeList.GetGrantees()[i], "") +// // encryptedAccessKey := "exampleEncryptedAccessKey" +// // _, err := actMock.Add(actMockRootHash, []byte(lookupKey), []byte(encryptedAccessKey)) +// // if err != nil { +// // t.Fatalf("Failed to add grantee to content using ActMock: %v", err) +// // } + +// // // Validate the resulting ACT +// // encryptedAccessKeyFromMock, err := actMock.Get(actMockRootHash, []byte(lookupKey)) +// // if err != nil { +// // t.Fatalf("Failed to retrieve encrypted access key from ActMock: %v", err) +// // } +// // encryptedAccessKeyFromMockBytes, _ := hex.DecodeString(encryptedAccessKeyFromMock) +// // if string(encryptedAccessKeyFromMockBytes) != encryptedAccessKey { +// // t.Errorf("Encrypted access key retrieved from ActMock doesn't match expected value") +// // } +// // } + +// // al.Add_New_Grantee_To_Content(actMock, encryptedRef, id0.PublicKey, testGranteeList.GetGrantees()[i]) +// // } diff --git a/pkg/dynamicaccess/controller.go b/pkg/dynamicaccess/controller.go index 3dfee5bff73..1637234d9c2 100644 --- a/pkg/dynamicaccess/controller.go +++ b/pkg/dynamicaccess/controller.go @@ -1,18 +1,46 @@ package dynamicaccess +import ( + "crypto/ecdsa" + + "github.com/ethersphere/bee/pkg/swarm" +) + type Controller interface { + DownloadHandler(timestamp int64, enryptedRef swarm.Address, publisher *ecdsa.PublicKey, tag string) (swarm.Address, error) + UploadHandler(ref swarm.Address, publisher *ecdsa.PublicKey, topic string) (swarm.Address, error) } type defaultController struct { - histrory History - uploader Publish - grantee Grantee + history History + granteeManager GranteeManager + accessLogic AccessLogic +} + +func (c *defaultController) DownloadHandler(timestamp int64, enryptedRef swarm.Address, publisher *ecdsa.PublicKey, tag string) (swarm.Address, error) { + act, err := c.history.Lookup(timestamp) + if err != nil { + return swarm.EmptyAddress, err + } + addr, err := c.accessLogic.Get(act, enryptedRef, *publisher, tag) + return addr, err +} + +func (c *defaultController) UploadHandler(ref swarm.Address, publisher *ecdsa.PublicKey, topic string) (swarm.Address, error) { + act, _ := c.history.Lookup(0) + if act == nil { + // new feed + act = NewDefaultAct() + act = c.granteeManager.Publish(act, *publisher, topic) + } + //FIXME: check if ACT is consistent with the grantee list + return c.accessLogic.EncryptRef(act, *publisher, ref) } -func NewController(histrory History, uploader Publish, grantee Grantee) Controller { +func NewController(history History, granteeManager GranteeManager, accessLogic AccessLogic) Controller { return &defaultController{ - histrory: histrory, - uploader: uploader, - grantee: grantee, + history: history, + granteeManager: granteeManager, + accessLogic: accessLogic, } } diff --git a/pkg/dynamicaccess/controller_test.go b/pkg/dynamicaccess/controller_test.go new file mode 100644 index 00000000000..d043a18532c --- /dev/null +++ b/pkg/dynamicaccess/controller_test.go @@ -0,0 +1,99 @@ +package dynamicaccess_test + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "encoding/hex" + "testing" + "time" + + "github.com/ethersphere/bee/pkg/crypto" + "github.com/ethersphere/bee/pkg/dynamicaccess" + "github.com/ethersphere/bee/pkg/dynamicaccess/mock" + "github.com/ethersphere/bee/pkg/encryption" + "github.com/ethersphere/bee/pkg/swarm" + "golang.org/x/crypto/sha3" +) + +var hashFunc = sha3.NewLegacyKeccak256 + +func mockTestHistory(key, val []byte) dynamicaccess.History { + var ( + h = mock.NewHistory() + now = time.Now() + act = mock.NewActMock(nil, func(lookupKey []byte) []byte { + return val + }) + ) + // act.Add(key, val) + h.Insert(now.AddDate(-3, 0, 0).Unix(), act) + return h +} + +func TestDecrypt(t *testing.T) { + pk := getPrivateKey() + ak := encryption.Key([]byte("cica")) + + dh := dynamicaccess.NewDiffieHellman(pk) + aek, _ := dh.SharedSecret(&pk.PublicKey, "", []byte{1}) + e2 := encryption.New(aek, 0, uint32(0), hashFunc) + peak, _ := e2.Encrypt(ak) + + h := mockTestHistory(nil, peak) + al := setupAccessLogic(pk) + gm := dynamicaccess.NewGranteeManager(al) + c := dynamicaccess.NewController(h, gm, al) + eref, ref := prepareEncryptedChunkReference(ak) + // ech := al.EncryptRef(ch, "tag") + + ts := int64(0) + addr, err := c.DownloadHandler(ts, eref, &pk.PublicKey, "tag") + if err != nil { + t.Fatalf("DownloadHandler() returned an error: %v", err) + } + if !addr.Equal(ref) { + t.Fatalf("Decrypted chunk address: %s is not the expected: %s", addr, ref) + } +} + +func TestEncrypt(t *testing.T) { + pk := getPrivateKey() + ak := encryption.Key([]byte("cica")) + + dh := dynamicaccess.NewDiffieHellman(pk) + aek, _ := dh.SharedSecret(&pk.PublicKey, "", []byte{1}) + e2 := encryption.New(aek, 0, uint32(0), hashFunc) + peak, _ := e2.Encrypt(ak) + + h := mockTestHistory(nil, peak) + al := setupAccessLogic(pk) + gm := dynamicaccess.NewGranteeManager(al) + c := dynamicaccess.NewController(h, gm, al) + eref, ref := prepareEncryptedChunkReference(ak) + + key1, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + gm.Add("topic", []*ecdsa.PublicKey{&key1.PublicKey}) + + addr, _ := c.UploadHandler(ref, &pk.PublicKey, "topic") + if !addr.Equal(eref) { + t.Fatalf("Decrypted chunk address: %s is not the expected: %s", addr, eref) + } +} + +func prepareEncryptedChunkReference(ak []byte) (swarm.Address, swarm.Address) { + addr, _ := hex.DecodeString("f7b1a45b70ee91d3dbfd98a2a692387f24db7279a9c96c447409e9205cf265baef29bf6aa294264762e33f6a18318562c86383dd8bfea2cec14fae08a8039bf3") + e1 := encryption.New(ak, 0, uint32(0), hashFunc) + ech, err := e1.Encrypt(addr) + if err != nil { + return swarm.EmptyAddress, swarm.NewAddress(addr) + } + return swarm.NewAddress(ech), swarm.NewAddress(addr) +} + +func getPrivateKey() *ecdsa.PrivateKey { + data, _ := hex.DecodeString("c786dd84b61485de12146fd9c4c02d87e8fd95f0542765cb7fc3d2e428c0bcfa") + + privKey, _ := crypto.DecodeSecp256k1PrivateKey(data) + return privKey +} diff --git a/pkg/dynamicaccess/grantee.go b/pkg/dynamicaccess/grantee.go index bb39899e012..2a07d84b3ed 100644 --- a/pkg/dynamicaccess/grantee.go +++ b/pkg/dynamicaccess/grantee.go @@ -1,6 +1,8 @@ package dynamicaccess -import "crypto/ecdsa" +import ( + "crypto/ecdsa" +) type Grantee interface { //? ÁTBESZÉLNI @@ -9,18 +11,17 @@ type Grantee interface { // RevokeList(topic string, removeList []string, addList []string) (string, error) // RevokeGrantees(topic string, removeList []string) (string, error) - AddGrantees(addList []ecdsa.PublicKey) ([]ecdsa.PublicKey, error) - RemoveGrantees(removeList []ecdsa.PublicKey) ([]ecdsa.PublicKey, error) - GetGrantees() []ecdsa.PublicKey + AddGrantees(topic string, addList []*ecdsa.PublicKey) error + RemoveGrantees(topic string, removeList []*ecdsa.PublicKey) error + GetGrantees(topic string) []*ecdsa.PublicKey } type defaultGrantee struct { - topic string //lint:ignore U1000 Ignore unused struct field - grantees []ecdsa.PublicKey // Modified field name to start with an uppercase letter + grantees map[string][]*ecdsa.PublicKey // Modified field name to start with an uppercase letter } -func (g *defaultGrantee) GetGrantees() []ecdsa.PublicKey { - return g.grantees +func (g *defaultGrantee) GetGrantees(topic string) []*ecdsa.PublicKey { + return g.grantees[topic] } // func (g *defaultGrantee) Revoke(topic string) error { @@ -35,22 +36,22 @@ func (g *defaultGrantee) GetGrantees() []ecdsa.PublicKey { // return nil // } -func (g *defaultGrantee) AddGrantees(addList []ecdsa.PublicKey) ([]ecdsa.PublicKey, error) { - g.grantees = append(g.grantees, addList...) - return g.grantees, nil +func (g *defaultGrantee) AddGrantees(topic string, addList []*ecdsa.PublicKey) error { + g.grantees[topic] = append(g.grantees[topic], addList...) + return nil } -func (g *defaultGrantee) RemoveGrantees(removeList []ecdsa.PublicKey) ([]ecdsa.PublicKey, error) { +func (g *defaultGrantee) RemoveGrantees(topic string, removeList []*ecdsa.PublicKey) error { for _, remove := range removeList { - for i, grantee := range g.grantees { + for i, grantee := range g.grantees[topic] { if grantee == remove { - g.grantees = append(g.grantees[:i], g.grantees[i+1:]...) + g.grantees[topic] = append(g.grantees[topic][:i], g.grantees[topic][i+1:]...) } } } - return g.grantees, nil + return nil } func NewGrantee() Grantee { - return &defaultGrantee{} + return &defaultGrantee{grantees: make(map[string][]*ecdsa.PublicKey)} } diff --git a/pkg/dynamicaccess/grantee_manager.go b/pkg/dynamicaccess/grantee_manager.go new file mode 100644 index 00000000000..9172a05db03 --- /dev/null +++ b/pkg/dynamicaccess/grantee_manager.go @@ -0,0 +1,41 @@ +package dynamicaccess + +import "crypto/ecdsa" + +type GranteeManager interface { + Get(topic string) []*ecdsa.PublicKey + Add(topic string, addList []*ecdsa.PublicKey) error + Publish(act Act, publisher ecdsa.PublicKey, topic string) Act + + // HandleGrantees(topic string, addList, removeList []*ecdsa.PublicKey) *Act + + // Load(grantee Grantee) + // Save() +} + +var _ GranteeManager = (*granteeManager)(nil) + +type granteeManager struct { + accessLogic AccessLogic + granteeList Grantee +} + +func NewGranteeManager(al AccessLogic) *granteeManager { + return &granteeManager{accessLogic: al, granteeList: NewGrantee()} +} + +func (gm *granteeManager) Get(topic string) []*ecdsa.PublicKey { + return gm.granteeList.GetGrantees(topic) +} + +func (gm *granteeManager) Add(topic string, addList []*ecdsa.PublicKey) error { + return gm.granteeList.AddGrantees(topic, addList) +} + +func (gm *granteeManager) Publish(act Act, publisher ecdsa.PublicKey, topic string) Act { + gm.accessLogic.AddPublisher(act, publisher, "") + for _, grantee := range gm.granteeList.GetGrantees(topic) { + gm.accessLogic.Add_New_Grantee_To_Content(act, publisher, *grantee) + } + return act +} diff --git a/pkg/dynamicaccess/grantee_manager_test.go b/pkg/dynamicaccess/grantee_manager_test.go new file mode 100644 index 00000000000..d8e806498e0 --- /dev/null +++ b/pkg/dynamicaccess/grantee_manager_test.go @@ -0,0 +1,41 @@ +package dynamicaccess_test + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "fmt" + "testing" + + "github.com/ethersphere/bee/pkg/dynamicaccess" +) + +func setupAccessLogic(privateKey *ecdsa.PrivateKey) dynamicaccess.AccessLogic { + // privateKey, err := crypto.GenerateSecp256k1Key() + // if err != nil { + // errors.New("error creating private key") + // } + diffieHellman := dynamicaccess.NewDiffieHellman(privateKey) + al := dynamicaccess.NewAccessLogic(diffieHellman) + + return al +} + +func TestAdd(t *testing.T) { + act := dynamicaccess.NewDefaultAct() + m := dynamicaccess.NewGranteeManager(setupAccessLogic(getPrivateKey())) + pub, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + + id1, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + id2, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + err := m.Add("topic", []*ecdsa.PublicKey{&id1.PublicKey}) + if err != nil { + t.Errorf("Add() returned an error") + } + err = m.Add("topic", []*ecdsa.PublicKey{&id2.PublicKey}) + if err != nil { + t.Errorf("Add() returned an error") + } + m.Publish(act, pub.PublicKey, "topic") + fmt.Println("") +} diff --git a/pkg/dynamicaccess/grantee_test.go b/pkg/dynamicaccess/grantee_test.go index 5140216d2a2..2d795ab2eef 100644 --- a/pkg/dynamicaccess/grantee_test.go +++ b/pkg/dynamicaccess/grantee_test.go @@ -1,4 +1,4 @@ -package dynamicaccess +package dynamicaccess_test import ( "crypto/ecdsa" @@ -6,6 +6,8 @@ import ( "crypto/rand" "reflect" "testing" + + "github.com/ethersphere/bee/pkg/dynamicaccess" ) // func TestGranteeRevoke(t *testing.T) { @@ -31,16 +33,16 @@ import ( func TestGranteeAddGrantees(t *testing.T) { // Create a new Grantee - grantee := NewGrantee() + grantee := dynamicaccess.NewGrantee() // Generate some dummy ecdsa.PublicKey values key1, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) key2, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) // Add the keys to the grantee - addList := []ecdsa.PublicKey{key1.PublicKey, key2.PublicKey} - grantees, err := grantee.AddGrantees(addList) - + addList := []*ecdsa.PublicKey{&key1.PublicKey, &key2.PublicKey} + err := grantee.AddGrantees("topicName", addList) + grantees := grantee.GetGrantees("topicName") // Check for errors if err != nil { t.Fatalf("Expected no error, got %v", err) @@ -54,19 +56,20 @@ func TestGranteeAddGrantees(t *testing.T) { func TestRemoveGrantees(t *testing.T) { // Create a new Grantee - grantee := NewGrantee() + grantee := dynamicaccess.NewGrantee() // Generate some dummy ecdsa.PublicKey values key1, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) key2, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) // Add the keys to the grantee - addList := []ecdsa.PublicKey{key1.PublicKey, key2.PublicKey} - grantee.AddGrantees(addList) + addList := []*ecdsa.PublicKey{&key1.PublicKey, &key2.PublicKey} + grantee.AddGrantees("topicName", addList) // Remove one of the keys - removeList := []ecdsa.PublicKey{key1.PublicKey} - grantees, err := grantee.RemoveGrantees(removeList) + removeList := []*ecdsa.PublicKey{&key1.PublicKey} + err := grantee.RemoveGrantees("topicName", removeList) + grantees := grantee.GetGrantees("topicName") // Check for errors if err != nil { @@ -74,7 +77,7 @@ func TestRemoveGrantees(t *testing.T) { } // Check if the key was removed correctly - expectedGrantees := []ecdsa.PublicKey{key2.PublicKey} + expectedGrantees := []*ecdsa.PublicKey{&key2.PublicKey} if !reflect.DeepEqual(grantees, expectedGrantees) { t.Errorf("Expected grantees %v, got %v", expectedGrantees, grantees) } @@ -82,23 +85,21 @@ func TestRemoveGrantees(t *testing.T) { func TestGetGrantees(t *testing.T) { // Create a new Grantee - grantee := NewGrantee() + grantee := dynamicaccess.NewGrantee() // Generate some dummy ecdsa.PublicKey values key1, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) key2, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) // Add the keys to the grantee - addList := []ecdsa.PublicKey{key1.PublicKey, key2.PublicKey} - grantee.AddGrantees(addList) + addList := []*ecdsa.PublicKey{&key1.PublicKey, &key2.PublicKey} + grantee.AddGrantees("topicName", addList) // Get the grantees - grantees := grantee.GetGrantees() + grantees := grantee.GetGrantees("topicName") // Check if the grantees were returned correctly if !reflect.DeepEqual(grantees, addList) { t.Errorf("Expected grantees %v, got %v", addList, grantees) } } - - diff --git a/pkg/dynamicaccess/history_test.go b/pkg/dynamicaccess/history_test.go index 2a24e65d862..ac333edc919 100644 --- a/pkg/dynamicaccess/history_test.go +++ b/pkg/dynamicaccess/history_test.go @@ -11,7 +11,7 @@ import ( ) func TestHistoryLookup(t *testing.T) { - h := pretareTestHistory() + h := prepareTestHistory() now := time.Now() tests := []struct { @@ -38,7 +38,7 @@ func TestHistoryLookup(t *testing.T) { } } -func pretareTestHistory() dynamicaccess.History { +func prepareTestHistory() dynamicaccess.History { var ( h = mock.NewHistory() now = time.Now() diff --git a/pkg/dynamicaccess/mock/act.go b/pkg/dynamicaccess/mock/act.go index 1fd68caa399..a83fad36f8c 100644 --- a/pkg/dynamicaccess/mock/act.go +++ b/pkg/dynamicaccess/mock/act.go @@ -42,6 +42,9 @@ func (act *ActMock) Store(me manifest.Entry) { act.StoreFunc(me) } -func NewActMock() dynamicaccess.Act { - return &ActMock{} +func NewActMock(addFunc func(lookupKey []byte, encryptedAccessKey []byte) dynamicaccess.Act, getFunc func(lookupKey []byte) []byte) dynamicaccess.Act { + return &ActMock{ + AddFunc: addFunc, + GetFunc: getFunc, + } } From a2c39af89fecfc97c77c2c0acae56a2b796a9f50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferenc=20S=C3=A1rai?= Date: Tue, 19 Mar 2024 11:06:08 +0100 Subject: [PATCH 06/34] (refactor): from `Get` to `Lookup` to improve clarity and consistency. The changes have been made in the `accesslogic.go`, `act.go`, `act_test.go`, `history_test.go`, and `mock/act.go` files. (#13) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ferenc Sárai --- pkg/dynamicaccess/accesslogic.go | 2 +- pkg/dynamicaccess/act.go | 4 ++-- pkg/dynamicaccess/act_test.go | 8 ++++---- pkg/dynamicaccess/history_test.go | 2 +- pkg/dynamicaccess/mock/act.go | 18 +++++++++--------- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/pkg/dynamicaccess/accesslogic.go b/pkg/dynamicaccess/accesslogic.go index b3c01974f24..c4c6e7438a8 100644 --- a/pkg/dynamicaccess/accesslogic.go +++ b/pkg/dynamicaccess/accesslogic.go @@ -115,7 +115,7 @@ func (al *DefaultAccessLogic) getAccessKeyDecriptionKey(publisher ecdsa.PublicKe } func (al *DefaultAccessLogic) getEncryptedAccessKey(act Act, lookup_key []byte) ([]byte, error) { - return act.Get(lookup_key), nil + return act.Lookup(lookup_key), nil } func (al *DefaultAccessLogic) Get(act Act, encryped_ref swarm.Address, publisher ecdsa.PublicKey, tag string) (swarm.Address, error) { diff --git a/pkg/dynamicaccess/act.go b/pkg/dynamicaccess/act.go index b63b1dafc6b..2c1765d1d54 100644 --- a/pkg/dynamicaccess/act.go +++ b/pkg/dynamicaccess/act.go @@ -9,7 +9,7 @@ import ( type Act interface { Add(lookupKey []byte, encryptedAccessKey []byte) Act - Get(lookupKey []byte) []byte + Lookup(lookupKey []byte) []byte Load(lookupKey []byte) manifest.Entry Store(me manifest.Entry) } @@ -25,7 +25,7 @@ func (act *defaultAct) Add(lookupKey []byte, encryptedAccessKey []byte) Act { return act } -func (act *defaultAct) Get(lookupKey []byte) []byte { +func (act *defaultAct) Lookup(lookupKey []byte) []byte { if key, ok := act.container[hex.EncodeToString(lookupKey)]; ok { bytes, err := hex.DecodeString(key) if err == nil { diff --git a/pkg/dynamicaccess/act_test.go b/pkg/dynamicaccess/act_test.go index 0e5ca853f0b..a46cc3b228c 100644 --- a/pkg/dynamicaccess/act_test.go +++ b/pkg/dynamicaccess/act_test.go @@ -17,7 +17,7 @@ import ( "github.com/ethersphere/bee/pkg/swarm" ) -func TestActAddGet(t *testing.T) { +func TestActAddLookup(t *testing.T) { act := dynamicaccess.NewDefaultAct() lookupKey := swarm.RandAddress(t).Bytes() encryptedAccesskey := swarm.RandAddress(t).Bytes() @@ -26,7 +26,7 @@ func TestActAddGet(t *testing.T) { t.Error("Add() should return an act") } - key := act.Get(lookupKey) + key := act.Lookup(lookupKey) if !bytes.Equal(key, encryptedAccesskey) { t.Errorf("Get() value is not the expected %s != %s", key, encryptedAccesskey) } @@ -73,7 +73,7 @@ func TestActWithManifest(t *testing.T) { actualAct := dynamicaccess.NewDefaultAct() actualAct.Store(actualMe) - actualEak := actualAct.Get(lookupKey) + actualEak := actualAct.Lookup(lookupKey) if !bytes.Equal(actualEak, encryptedAccesskey) { t.Errorf("actualAct.Store() value is not the expected %s != %s", actualEak, encryptedAccesskey) } @@ -89,7 +89,7 @@ func TestActStore(t *testing.T) { me := manifest.NewEntry(swarm.NewAddress(lookupKey), mp) act := dynamicaccess.NewDefaultAct() act.Store(me) - eak := act.Get(lookupKey) + eak := act.Lookup(lookupKey) if !bytes.Equal(eak, encryptedAccesskey) { t.Errorf("Store() value is not the expected %s != %s", eak, encryptedAccesskey) diff --git a/pkg/dynamicaccess/history_test.go b/pkg/dynamicaccess/history_test.go index ac333edc919..340b2adcb35 100644 --- a/pkg/dynamicaccess/history_test.go +++ b/pkg/dynamicaccess/history_test.go @@ -32,7 +32,7 @@ func TestHistoryLookup(t *testing.T) { for _, tt := range tests { t.Run("", func(t *testing.T) { actAt, _ := h.Lookup(tt.input) - output := actAt.Get([]byte("key1")) + output := actAt.Lookup([]byte("key1")) assert.Equal(t, output, hex.EncodeToString([]byte(tt.expected))) }) } diff --git a/pkg/dynamicaccess/mock/act.go b/pkg/dynamicaccess/mock/act.go index a83fad36f8c..8bcfb8ff0b9 100644 --- a/pkg/dynamicaccess/mock/act.go +++ b/pkg/dynamicaccess/mock/act.go @@ -6,10 +6,10 @@ import ( ) type ActMock struct { - AddFunc func(lookupKey []byte, encryptedAccessKey []byte) dynamicaccess.Act - GetFunc func(lookupKey []byte) []byte - LoadFunc func(lookupKey []byte) manifest.Entry - StoreFunc func(me manifest.Entry) + AddFunc func(lookupKey []byte, encryptedAccessKey []byte) dynamicaccess.Act + LookupFunc func(lookupKey []byte) []byte + LoadFunc func(lookupKey []byte) manifest.Entry + StoreFunc func(me manifest.Entry) } var _ dynamicaccess.Act = (*ActMock)(nil) @@ -21,11 +21,11 @@ func (act *ActMock) Add(lookupKey []byte, encryptedAccessKey []byte) dynamicacce return act.AddFunc(lookupKey, encryptedAccessKey) } -func (act *ActMock) Get(lookupKey []byte) []byte { - if act.GetFunc == nil { +func (act *ActMock) Lookup(lookupKey []byte) []byte { + if act.LookupFunc == nil { return make([]byte, 0) } - return act.GetFunc(lookupKey) + return act.LookupFunc(lookupKey) } func (act *ActMock) Load(lookupKey []byte) manifest.Entry { @@ -44,7 +44,7 @@ func (act *ActMock) Store(me manifest.Entry) { func NewActMock(addFunc func(lookupKey []byte, encryptedAccessKey []byte) dynamicaccess.Act, getFunc func(lookupKey []byte) []byte) dynamicaccess.Act { return &ActMock{ - AddFunc: addFunc, - GetFunc: getFunc, + AddFunc: addFunc, + LookupFunc: getFunc, } } From bc5326c388897d88482d4ad25273b7f6c731dc4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferenc=20S=C3=A1rai?= Date: Tue, 19 Mar 2024 14:09:42 +0100 Subject: [PATCH 07/34] Act params rename doc (#14) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * (refactor): ACT interface params + add doc comments * Revert "(refactor): ACT interface params + add doc comments" This reverts commit ee8da04fe7468a4fa65bd390fa17f72f2e93d301. * (refactor): ACT interface params + add doc comments * (refactor): Add error to ACT interface methods --------- Co-authored-by: Ferenc Sárai --- pkg/dynamicaccess/accesslogic.go | 6 +++- pkg/dynamicaccess/act.go | 44 ++++++++++++++++++---------- pkg/dynamicaccess/act_test.go | 26 +++++++++------- pkg/dynamicaccess/controller_test.go | 4 +-- pkg/dynamicaccess/history_test.go | 2 +- pkg/dynamicaccess/mock/act.go | 37 +++++++++++++---------- 6 files changed, 73 insertions(+), 46 deletions(-) diff --git a/pkg/dynamicaccess/accesslogic.go b/pkg/dynamicaccess/accesslogic.go index c4c6e7438a8..256dbd9f5ef 100644 --- a/pkg/dynamicaccess/accesslogic.go +++ b/pkg/dynamicaccess/accesslogic.go @@ -115,7 +115,11 @@ func (al *DefaultAccessLogic) getAccessKeyDecriptionKey(publisher ecdsa.PublicKe } func (al *DefaultAccessLogic) getEncryptedAccessKey(act Act, lookup_key []byte) ([]byte, error) { - return act.Lookup(lookup_key), nil + val, err := act.Lookup(lookup_key) + if err != nil { + return []byte{}, err + } + return val, nil } func (al *DefaultAccessLogic) Get(act Act, encryped_ref swarm.Address, publisher ecdsa.PublicKey, tag string) (swarm.Address, error) { diff --git a/pkg/dynamicaccess/act.go b/pkg/dynamicaccess/act.go index 2c1765d1d54..07728ebca67 100644 --- a/pkg/dynamicaccess/act.go +++ b/pkg/dynamicaccess/act.go @@ -1,3 +1,7 @@ +// Copyright 2024 The Swarm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package dynamicaccess import ( @@ -7,11 +11,19 @@ import ( "github.com/ethersphere/bee/pkg/swarm" ) +// Act represents an interface for accessing and manipulating data. type Act interface { - Add(lookupKey []byte, encryptedAccessKey []byte) Act - Lookup(lookupKey []byte) []byte - Load(lookupKey []byte) manifest.Entry - Store(me manifest.Entry) + // Add adds a key-value pair to the data store. + Add(key []byte, val []byte) error + + // Lookup retrieves the value associated with the given key from the data store. + Lookup(key []byte) ([]byte, error) + + // Load retrieves the manifest entry associated with the given key from the data store. + Load(key []byte) (manifest.Entry, error) + + // Store stores the given manifest entry in the data store. + Store(me manifest.Entry) error } var _ Act = (*defaultAct)(nil) @@ -20,32 +32,34 @@ type defaultAct struct { container map[string]string } -func (act *defaultAct) Add(lookupKey []byte, encryptedAccessKey []byte) Act { - act.container[hex.EncodeToString(lookupKey)] = hex.EncodeToString(encryptedAccessKey) - return act +func (act *defaultAct) Add(key []byte, val []byte) error { + act.container[hex.EncodeToString(key)] = hex.EncodeToString(val) + return nil } -func (act *defaultAct) Lookup(lookupKey []byte) []byte { - if key, ok := act.container[hex.EncodeToString(lookupKey)]; ok { +func (act *defaultAct) Lookup(key []byte) ([]byte, error) { + if key, ok := act.container[hex.EncodeToString(key)]; ok { bytes, err := hex.DecodeString(key) - if err == nil { - return bytes + if err != nil { + return nil, err } + return bytes, nil } - return make([]byte, 0) + return make([]byte, 0), nil } // to manifestEntry -func (act *defaultAct) Load(lookupKey []byte) manifest.Entry { - return manifest.NewEntry(swarm.NewAddress(lookupKey), act.container) +func (act *defaultAct) Load(key []byte) (manifest.Entry, error) { + return manifest.NewEntry(swarm.NewAddress(key), act.container), nil } // from manifestEntry -func (act *defaultAct) Store(me manifest.Entry) { +func (act *defaultAct) Store(me manifest.Entry) error { if act.container == nil { act.container = make(map[string]string) } act.container = me.Metadata() + return nil } func NewDefaultAct() Act { diff --git a/pkg/dynamicaccess/act_test.go b/pkg/dynamicaccess/act_test.go index a46cc3b228c..0bd807cbcc2 100644 --- a/pkg/dynamicaccess/act_test.go +++ b/pkg/dynamicaccess/act_test.go @@ -1,3 +1,7 @@ +// Copyright 2024 The Swarm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package dynamicaccess_test import ( @@ -21,12 +25,12 @@ func TestActAddLookup(t *testing.T) { act := dynamicaccess.NewDefaultAct() lookupKey := swarm.RandAddress(t).Bytes() encryptedAccesskey := swarm.RandAddress(t).Bytes() - act2 := act.Add(lookupKey, encryptedAccesskey) - if act2 == nil { - t.Error("Add() should return an act") + err := act.Add(lookupKey, encryptedAccesskey) + if err != nil { + t.Error("Add() should not return an error") } - key := act.Lookup(lookupKey) + key, _ := act.Lookup(lookupKey) if !bytes.Equal(key, encryptedAccesskey) { t.Errorf("Get() value is not the expected %s != %s", key, encryptedAccesskey) } @@ -46,12 +50,12 @@ func TestActWithManifest(t *testing.T) { act := dynamicaccess.NewDefaultAct() lookupKey := swarm.RandAddress(t).Bytes() encryptedAccesskey := swarm.RandAddress(t).Bytes() - act2 := act.Add(lookupKey, encryptedAccesskey) - if act2 == nil { - t.Error("Add() should return an act") + err = act.Add(lookupKey, encryptedAccesskey) + if err != nil { + t.Error("Add() should not return an error") } - actManifEntry := act.Load(lookupKey) + actManifEntry, _ := act.Load(lookupKey) if actManifEntry == nil { t.Error("Load() should return a manifest.Entry") } @@ -73,7 +77,7 @@ func TestActWithManifest(t *testing.T) { actualAct := dynamicaccess.NewDefaultAct() actualAct.Store(actualMe) - actualEak := actualAct.Lookup(lookupKey) + actualEak, _ := actualAct.Lookup(lookupKey) if !bytes.Equal(actualEak, encryptedAccesskey) { t.Errorf("actualAct.Store() value is not the expected %s != %s", actualEak, encryptedAccesskey) } @@ -89,7 +93,7 @@ func TestActStore(t *testing.T) { me := manifest.NewEntry(swarm.NewAddress(lookupKey), mp) act := dynamicaccess.NewDefaultAct() act.Store(me) - eak := act.Lookup(lookupKey) + eak, _ := act.Lookup(lookupKey) if !bytes.Equal(eak, encryptedAccesskey) { t.Errorf("Store() value is not the expected %s != %s", eak, encryptedAccesskey) @@ -102,7 +106,7 @@ func TestActLoad(t *testing.T) { lookupKey := swarm.RandAddress(t).Bytes() encryptedAccesskey := swarm.RandAddress(t).Bytes() act.Add(lookupKey, encryptedAccesskey) - me := act.Load(lookupKey) + me, _ := act.Load(lookupKey) eak := me.Metadata()[hex.EncodeToString(lookupKey)] diff --git a/pkg/dynamicaccess/controller_test.go b/pkg/dynamicaccess/controller_test.go index d043a18532c..d42df211fc9 100644 --- a/pkg/dynamicaccess/controller_test.go +++ b/pkg/dynamicaccess/controller_test.go @@ -22,8 +22,8 @@ func mockTestHistory(key, val []byte) dynamicaccess.History { var ( h = mock.NewHistory() now = time.Now() - act = mock.NewActMock(nil, func(lookupKey []byte) []byte { - return val + act = mock.NewActMock(nil, func(lookupKey []byte) ([]byte, error) { + return val, nil }) ) // act.Add(key, val) diff --git a/pkg/dynamicaccess/history_test.go b/pkg/dynamicaccess/history_test.go index 340b2adcb35..f7997d55fcd 100644 --- a/pkg/dynamicaccess/history_test.go +++ b/pkg/dynamicaccess/history_test.go @@ -32,7 +32,7 @@ func TestHistoryLookup(t *testing.T) { for _, tt := range tests { t.Run("", func(t *testing.T) { actAt, _ := h.Lookup(tt.input) - output := actAt.Lookup([]byte("key1")) + output, _ := actAt.Lookup([]byte("key1")) assert.Equal(t, output, hex.EncodeToString([]byte(tt.expected))) }) } diff --git a/pkg/dynamicaccess/mock/act.go b/pkg/dynamicaccess/mock/act.go index 8bcfb8ff0b9..4f320c13dce 100644 --- a/pkg/dynamicaccess/mock/act.go +++ b/pkg/dynamicaccess/mock/act.go @@ -1,3 +1,7 @@ +// Copyright 2024 The Swarm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package mock import ( @@ -6,43 +10,44 @@ import ( ) type ActMock struct { - AddFunc func(lookupKey []byte, encryptedAccessKey []byte) dynamicaccess.Act - LookupFunc func(lookupKey []byte) []byte - LoadFunc func(lookupKey []byte) manifest.Entry - StoreFunc func(me manifest.Entry) + AddFunc func(key []byte, val []byte) error + LookupFunc func(key []byte) ([]byte, error) + LoadFunc func(key []byte) (manifest.Entry, error) + StoreFunc func(me manifest.Entry) error } var _ dynamicaccess.Act = (*ActMock)(nil) -func (act *ActMock) Add(lookupKey []byte, encryptedAccessKey []byte) dynamicaccess.Act { +func (act *ActMock) Add(key []byte, val []byte) error { if act.AddFunc == nil { - return act + return nil } - return act.AddFunc(lookupKey, encryptedAccessKey) + return act.AddFunc(key, val) } -func (act *ActMock) Lookup(lookupKey []byte) []byte { +func (act *ActMock) Lookup(key []byte) ([]byte, error) { if act.LookupFunc == nil { - return make([]byte, 0) + return make([]byte, 0), nil } - return act.LookupFunc(lookupKey) + return act.LookupFunc(key) } -func (act *ActMock) Load(lookupKey []byte) manifest.Entry { +func (act *ActMock) Load(key []byte) (manifest.Entry, error) { if act.LoadFunc == nil { - return nil + return nil, nil } - return act.LoadFunc(lookupKey) + return act.LoadFunc(key) } -func (act *ActMock) Store(me manifest.Entry) { +func (act *ActMock) Store(me manifest.Entry) error { if act.StoreFunc == nil { - return + return nil } act.StoreFunc(me) + return nil } -func NewActMock(addFunc func(lookupKey []byte, encryptedAccessKey []byte) dynamicaccess.Act, getFunc func(lookupKey []byte) []byte) dynamicaccess.Act { +func NewActMock(addFunc func(key []byte, val []byte) error, getFunc func(key []byte) ([]byte, error)) dynamicaccess.Act { return &ActMock{ AddFunc: addFunc, LookupFunc: getFunc, From 67e826a0e1536077192068f08b8f610a1b62382b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A1lint=20Ujv=C3=A1ri?= <58116288+bosi95@users.noreply.github.com> Date: Tue, 19 Mar 2024 15:48:54 +0100 Subject: [PATCH 08/34] Move and refactor ACT diffieHellman to Session. Add Key and NewFromKeystore functions. (#16) --- pkg/dynamicaccess/accesslogic.go | 72 +++++------- pkg/dynamicaccess/controller_test.go | 12 +- pkg/dynamicaccess/diffieHellman.go | 31 ----- pkg/dynamicaccess/diffieHellman_test.go | 54 --------- pkg/dynamicaccess/grantee_manager_test.go | 4 +- pkg/dynamicaccess/mock/diffieHellman.go | 22 ---- pkg/dynamicaccess/mock/session.go | 41 +++++++ pkg/dynamicaccess/session.go | 49 ++++++++ pkg/dynamicaccess/session_test.go | 132 ++++++++++++++++++++++ 9 files changed, 261 insertions(+), 156 deletions(-) delete mode 100644 pkg/dynamicaccess/diffieHellman.go delete mode 100644 pkg/dynamicaccess/diffieHellman_test.go delete mode 100644 pkg/dynamicaccess/mock/diffieHellman.go create mode 100644 pkg/dynamicaccess/mock/session.go create mode 100644 pkg/dynamicaccess/session.go create mode 100644 pkg/dynamicaccess/session_test.go diff --git a/pkg/dynamicaccess/accesslogic.go b/pkg/dynamicaccess/accesslogic.go index 256dbd9f5ef..8f46256cd85 100644 --- a/pkg/dynamicaccess/accesslogic.go +++ b/pkg/dynamicaccess/accesslogic.go @@ -14,8 +14,7 @@ type AccessLogic interface { Get(act Act, encryped_ref swarm.Address, publisher ecdsa.PublicKey, tag string) (swarm.Address, error) EncryptRef(act Act, publisherPubKey ecdsa.PublicKey, ref swarm.Address) (swarm.Address, error) //Add(act *Act, ref string, publisher ecdsa.PublicKey, tag string) (string, error) - getLookUpKey(publisher ecdsa.PublicKey, tag string) ([]byte, error) - getAccessKeyDecriptionKey(publisher ecdsa.PublicKey, tag string) ([]byte, error) + getKeys(publicKey ecdsa.PublicKey) ([][]byte, error) getEncryptedAccessKey(act Act, lookup_key []byte) ([]byte, error) //createEncryptedAccessKey(ref string) Add_New_Grantee_To_Content(act Act, publisherPubKey, granteePubKey ecdsa.PublicKey) (Act, error) @@ -24,7 +23,7 @@ type AccessLogic interface { } type DefaultAccessLogic struct { - diffieHellman DiffieHellman + session Session //encryption encryption.Interface } @@ -32,8 +31,12 @@ type DefaultAccessLogic struct { func (al *DefaultAccessLogic) AddPublisher(act Act, publisher ecdsa.PublicKey, tag string) (Act, error) { access_key := encryption.GenerateRandomKey(encryption.KeyLength) - lookup_key, _ := al.getLookUpKey(publisher, "") - access_key_encryption_key, _ := al.getAccessKeyDecriptionKey(publisher, "") + keys, err := al.getKeys(publisher) + if err != nil { + return nil, err + } + lookup_key := keys[0] + access_key_encryption_key := keys[1] access_key_cipher := encryption.New(encryption.Key(access_key_encryption_key), 0, uint32(0), hashFunc) encrypted_access_key, _ := access_key_cipher.Encrypt(access_key) @@ -62,8 +65,12 @@ func (al *DefaultAccessLogic) Add_New_Grantee_To_Content(act Act, publisherPubKe // --Encrypt access key for new Grantee-- // 2 Diffie-Hellman for the Grantee - lookup_key, _ := al.getLookUpKey(granteePubKey, "") - access_key_encryption_key, _ := al.getAccessKeyDecriptionKey(granteePubKey, "") + keys, err := al.getKeys(granteePubKey) + if err != nil { + return nil, err + } + lookup_key := keys[0] + access_key_encryption_key := keys[1] // Encrypt the access key for the new Grantee cipher := encryption.New(encryption.Key(access_key_encryption_key), 0, uint32(0), hashFunc) @@ -76,8 +83,12 @@ func (al *DefaultAccessLogic) Add_New_Grantee_To_Content(act Act, publisherPubKe } func (al *DefaultAccessLogic) getAccessKey(act Act, publisherPubKey ecdsa.PublicKey) []byte { - publisher_lookup_key, _ := al.getLookUpKey(publisherPubKey, "") - publisher_ak_decryption_key, _ := al.getAccessKeyDecriptionKey(publisherPubKey, "") + keys, err := al.getKeys(publisherPubKey) + if err != nil { + return nil + } + publisher_lookup_key := keys[0] + publisher_ak_decryption_key := keys[1] access_key_decryption_cipher := encryption.New(encryption.Key(publisher_ak_decryption_key), 0, uint32(0), hashFunc) encrypted_ak, _ := al.getEncryptedAccessKey(act, publisher_lookup_key) @@ -93,25 +104,16 @@ func (al *DefaultAccessLogic) getAccessKey(act Act, publisherPubKey ecdsa.Public // func (al *DefaultAccessLogic) CreateAccessKey(reference string) { // } -func (al *DefaultAccessLogic) getLookUpKey(publisher ecdsa.PublicKey, tag string) ([]byte, error) { +func (al *DefaultAccessLogic) getKeys(publicKey ecdsa.PublicKey) ([][]byte, error) { + // Generate lookup key and access key decryption + oneByteArray := []byte{1} zeroByteArray := []byte{0} - // Generate lookup key using Diffie Hellman - lookup_key, err := al.diffieHellman.SharedSecret(&publisher, tag, zeroByteArray) - if err != nil { - return []byte{}, err - } - return lookup_key, nil - -} -func (al *DefaultAccessLogic) getAccessKeyDecriptionKey(publisher ecdsa.PublicKey, tag string) ([]byte, error) { - oneByteArray := []byte{1} - // Generate access key decryption key using Diffie Hellman - access_key_decryption_key, err := al.diffieHellman.SharedSecret(&publisher, tag, oneByteArray) + keys, err := al.session.Key(&publicKey, [][]byte{zeroByteArray, oneByteArray}) if err != nil { - return []byte{}, err + return [][]byte{}, err } - return access_key_decryption_key, nil + return keys, nil } func (al *DefaultAccessLogic) getEncryptedAccessKey(act Act, lookup_key []byte) ([]byte, error) { @@ -124,14 +126,12 @@ func (al *DefaultAccessLogic) getEncryptedAccessKey(act Act, lookup_key []byte) func (al *DefaultAccessLogic) Get(act Act, encryped_ref swarm.Address, publisher ecdsa.PublicKey, tag string) (swarm.Address, error) { - lookup_key, err := al.getLookUpKey(publisher, tag) - if err != nil { - return swarm.EmptyAddress, err - } - access_key_decryption_key, err := al.getAccessKeyDecriptionKey(publisher, tag) + keys, err := al.getKeys(publisher) if err != nil { return swarm.EmptyAddress, err } + lookup_key := keys[0] + access_key_decryption_key := keys[1] // Lookup encrypted access key from the ACT manifest @@ -157,18 +157,8 @@ func (al *DefaultAccessLogic) Get(act Act, encryped_ref swarm.Address, publisher return swarm.NewAddress(ref), nil } -func NewAccessLogic(diffieHellman DiffieHellman) AccessLogic { +func NewAccessLogic(s Session) AccessLogic { return &DefaultAccessLogic{ - diffieHellman: diffieHellman, + session: s, } } - -// ------- -// act: &mock.ContainerMock{ -// AddFunc: func(ref string, publisher string, tag string) error { -// return nil -// }, -// GetFunc: func(ref string, publisher string, tag string) (string, error) { -// return "", nil -// }, -// }, diff --git a/pkg/dynamicaccess/controller_test.go b/pkg/dynamicaccess/controller_test.go index d42df211fc9..30e17cde542 100644 --- a/pkg/dynamicaccess/controller_test.go +++ b/pkg/dynamicaccess/controller_test.go @@ -35,9 +35,9 @@ func TestDecrypt(t *testing.T) { pk := getPrivateKey() ak := encryption.Key([]byte("cica")) - dh := dynamicaccess.NewDiffieHellman(pk) - aek, _ := dh.SharedSecret(&pk.PublicKey, "", []byte{1}) - e2 := encryption.New(aek, 0, uint32(0), hashFunc) + si := dynamicaccess.NewDefaultSession(pk) + aek, _ := si.Key(&pk.PublicKey, [][]byte{{1}}) + e2 := encryption.New(aek[0], 0, uint32(0), hashFunc) peak, _ := e2.Encrypt(ak) h := mockTestHistory(nil, peak) @@ -61,9 +61,9 @@ func TestEncrypt(t *testing.T) { pk := getPrivateKey() ak := encryption.Key([]byte("cica")) - dh := dynamicaccess.NewDiffieHellman(pk) - aek, _ := dh.SharedSecret(&pk.PublicKey, "", []byte{1}) - e2 := encryption.New(aek, 0, uint32(0), hashFunc) + si := dynamicaccess.NewDefaultSession(pk) + aek, _ := si.Key(&pk.PublicKey, [][]byte{{1}}) + e2 := encryption.New(aek[0], 0, uint32(0), hashFunc) peak, _ := e2.Encrypt(ak) h := mockTestHistory(nil, peak) diff --git a/pkg/dynamicaccess/diffieHellman.go b/pkg/dynamicaccess/diffieHellman.go deleted file mode 100644 index a9ff7f7f8cd..00000000000 --- a/pkg/dynamicaccess/diffieHellman.go +++ /dev/null @@ -1,31 +0,0 @@ -package dynamicaccess - -import ( - "crypto/ecdsa" - "errors" - - "github.com/ethersphere/bee/pkg/crypto" -) - -type DiffieHellman interface { - SharedSecret(publicKey *ecdsa.PublicKey, tag string, moment []byte) ([]byte, error) // tag- topic? -} - -var _ DiffieHellman = (*defaultDiffieHellman)(nil) - -type defaultDiffieHellman struct { - key *ecdsa.PrivateKey -} - -func (dh *defaultDiffieHellman) SharedSecret(publicKey *ecdsa.PublicKey, tag string, salt []byte) ([]byte, error) { - x, _ := publicKey.Curve.ScalarMult(publicKey.X, publicKey.Y, dh.key.D.Bytes()) - if x == nil { - return nil, errors.New("shared secret is point at infinity") - } - return crypto.LegacyKeccak256(append(x.Bytes(), salt...)) -} - -func NewDiffieHellman(key *ecdsa.PrivateKey) DiffieHellman { - return &defaultDiffieHellman{key: key} - -} diff --git a/pkg/dynamicaccess/diffieHellman_test.go b/pkg/dynamicaccess/diffieHellman_test.go deleted file mode 100644 index 5c80592b3ec..00000000000 --- a/pkg/dynamicaccess/diffieHellman_test.go +++ /dev/null @@ -1,54 +0,0 @@ -package dynamicaccess_test - -import ( - "bytes" - "crypto/rand" - "encoding/hex" - "io" - "testing" - - "github.com/ethersphere/bee/pkg/crypto" - "github.com/ethersphere/bee/pkg/dynamicaccess" -) - -func TestSharedSecret(t *testing.T) { - pk, _ := crypto.GenerateSecp256k1Key() - _, err := dynamicaccess.NewDiffieHellman(pk).SharedSecret(&pk.PublicKey, "", nil) - if err != nil { - t.Errorf("Error generating shared secret: %v", err) - } -} - -func TestECDHCorrect(t *testing.T) { - t.Parallel() - - key1, err := crypto.GenerateSecp256k1Key() - if err != nil { - t.Fatal(err) - } - dh1 := dynamicaccess.NewDiffieHellman(key1) - - key2, err := crypto.GenerateSecp256k1Key() - if err != nil { - t.Fatal(err) - } - dh2 := dynamicaccess.NewDiffieHellman(key2) - - moment := make([]byte, 1) - if _, err := io.ReadFull(rand.Reader, moment); err != nil { - t.Fatal(err) - } - - shared1, err := dh1.SharedSecret(&key2.PublicKey, "", moment) - if err != nil { - t.Fatal(err) - } - shared2, err := dh2.SharedSecret(&key1.PublicKey, "", moment) - if err != nil { - t.Fatal(err) - } - - if !bytes.Equal(shared1, shared2) { - t.Fatalf("shared secrets do not match %s, %s", hex.EncodeToString(shared1), hex.EncodeToString(shared2)) - } -} diff --git a/pkg/dynamicaccess/grantee_manager_test.go b/pkg/dynamicaccess/grantee_manager_test.go index d8e806498e0..4b55400f465 100644 --- a/pkg/dynamicaccess/grantee_manager_test.go +++ b/pkg/dynamicaccess/grantee_manager_test.go @@ -15,8 +15,8 @@ func setupAccessLogic(privateKey *ecdsa.PrivateKey) dynamicaccess.AccessLogic { // if err != nil { // errors.New("error creating private key") // } - diffieHellman := dynamicaccess.NewDiffieHellman(privateKey) - al := dynamicaccess.NewAccessLogic(diffieHellman) + si := dynamicaccess.NewDefaultSession(privateKey) + al := dynamicaccess.NewAccessLogic(si) return al } diff --git a/pkg/dynamicaccess/mock/diffieHellman.go b/pkg/dynamicaccess/mock/diffieHellman.go deleted file mode 100644 index 91601026893..00000000000 --- a/pkg/dynamicaccess/mock/diffieHellman.go +++ /dev/null @@ -1,22 +0,0 @@ -package mock - -import ( - "crypto/ecdsa" -) - -type DiffieHellmanMock struct { - SharedSecretFunc func(publicKey *ecdsa.PublicKey, tag string, salt []byte) ([]byte, error) - key *ecdsa.PrivateKey -} - -func (dhm *DiffieHellmanMock) SharedSecret(publicKey *ecdsa.PublicKey, tag string, salt []byte) ([]byte, error) { - if dhm.SharedSecretFunc == nil { - return nil, nil - } - return dhm.SharedSecretFunc(publicKey, tag, salt) - -} - -func NewDiffieHellmanMock(key *ecdsa.PrivateKey) *DiffieHellmanMock { - return &DiffieHellmanMock{key: key} -} diff --git a/pkg/dynamicaccess/mock/session.go b/pkg/dynamicaccess/mock/session.go new file mode 100644 index 00000000000..ba3e3f8c8f2 --- /dev/null +++ b/pkg/dynamicaccess/mock/session.go @@ -0,0 +1,41 @@ +package mock + +import ( + "crypto/ecdsa" + + "github.com/ethersphere/bee/pkg/crypto" + "github.com/ethersphere/bee/pkg/keystore" +) + +type SessionMock struct { + KeyFunc func(publicKey *ecdsa.PublicKey, nonces [][]byte) ([][]byte, error) + key *ecdsa.PrivateKey +} + +func (s *SessionMock) Key(publicKey *ecdsa.PublicKey, nonces [][]byte) ([][]byte, error) { + if s.KeyFunc == nil { + return nil, nil + } + return s.KeyFunc(publicKey, nonces) + +} + +func NewSessionMock(key *ecdsa.PrivateKey) *SessionMock { + return &SessionMock{key: key} +} + +func NewFromKeystore( + ks keystore.Service, + tag, + password string, + keyFunc func(publicKey *ecdsa.PublicKey, nonces [][]byte) ([][]byte, error), +) *SessionMock { + key, created, err := ks.Key(tag, password, crypto.EDGSecp256_K1) + if !created || err != nil { + return nil + } + return &SessionMock{ + key: key, + KeyFunc: keyFunc, + } +} diff --git a/pkg/dynamicaccess/session.go b/pkg/dynamicaccess/session.go new file mode 100644 index 00000000000..95a3c07e87b --- /dev/null +++ b/pkg/dynamicaccess/session.go @@ -0,0 +1,49 @@ +package dynamicaccess + +import ( + "crypto/ecdsa" + "errors" + + "github.com/ethersphere/bee/pkg/crypto" + "github.com/ethersphere/bee/pkg/keystore" +) + +// Session represents an interface for a Diffie-Helmann key derivation +type Session interface { + // Key returns a derived key for each nonce + Key(publicKey *ecdsa.PublicKey, nonces [][]byte) ([][]byte, error) +} + +var _ Session = (*session)(nil) + +type session struct { + key *ecdsa.PrivateKey +} + +func (s *session) Key(publicKey *ecdsa.PublicKey, nonces [][]byte) ([][]byte, error) { + x, _ := publicKey.Curve.ScalarMult(publicKey.X, publicKey.Y, s.key.D.Bytes()) + if x == nil { + return nil, errors.New("shared secret is point at infinity") + } + + keys := make([][]byte, len(nonces)) + for _, nonce := range nonces { + key, err := crypto.LegacyKeccak256(append(x.Bytes(), nonce...)) + if err != nil { + return nil, err + } + keys = append(keys, key) + } + + return keys, nil +} + +func NewDefaultSession(key *ecdsa.PrivateKey) Session { + return &session{ + key: key, + } +} + +func NewFromKeystore(ks keystore.Service, tag, password string) Session { + return nil +} diff --git a/pkg/dynamicaccess/session_test.go b/pkg/dynamicaccess/session_test.go new file mode 100644 index 00000000000..0cfee7691da --- /dev/null +++ b/pkg/dynamicaccess/session_test.go @@ -0,0 +1,132 @@ +package dynamicaccess_test + +import ( + "bytes" + "crypto/ecdsa" + "crypto/rand" + "encoding/hex" + "io" + "testing" + + "github.com/ethersphere/bee/pkg/crypto" + "github.com/ethersphere/bee/pkg/dynamicaccess" + "github.com/ethersphere/bee/pkg/dynamicaccess/mock" + memkeystore "github.com/ethersphere/bee/pkg/keystore/mem" +) + +func mockKeyFunc(publicKey *ecdsa.PublicKey, nonces [][]byte) ([][]byte, error) { + return [][]byte{{1}}, nil +} + +func TestSessionNewDefaultSession(t *testing.T) { + pk, err := crypto.GenerateSecp256k1Key() + if err != nil { + t.Fatalf("Error generating private key: %v", err) + } + si := dynamicaccess.NewDefaultSession(pk) + if si == nil { + t.Fatal("Session instance is nil") + } +} + +func TestSessionNewFromKeystore(t *testing.T) { + ks := memkeystore.New() + si := mock.NewFromKeystore(ks, "tag", "password", mockKeyFunc) + if si == nil { + t.Fatal("Session instance is nil") + } +} + +func TestSessionKey(t *testing.T) { + t.Parallel() + + key1, err := crypto.GenerateSecp256k1Key() + if err != nil { + t.Fatal(err) + } + si1 := dynamicaccess.NewDefaultSession(key1) + + key2, err := crypto.GenerateSecp256k1Key() + if err != nil { + t.Fatal(err) + } + si2 := dynamicaccess.NewDefaultSession(key2) + + nonces := make([][]byte, 1) + if _, err := io.ReadFull(rand.Reader, nonces[0]); err != nil { + t.Fatal(err) + } + + keys1, err := si1.Key(&key2.PublicKey, nonces) + if err != nil { + t.Fatal(err) + } + keys2, err := si2.Key(&key1.PublicKey, nonces) + if err != nil { + t.Fatal(err) + } + + if !bytes.Equal(keys1[0], keys2[0]) { + t.Fatalf("shared secrets do not match %s, %s", hex.EncodeToString(keys1[0]), hex.EncodeToString(keys2[0])) + } +} + +func TestSessionKeyFromKeystore(t *testing.T) { + t.Parallel() + + ks := memkeystore.New() + tag1 := "tag1" + tag2 := "tag2" + password1 := "password1" + password2 := "password2" + + si1 := mock.NewFromKeystore(ks, tag1, password1, mockKeyFunc) + exists, err := ks.Exists(tag1) + if err != nil { + t.Fatal(err) + } + if !exists { + t.Fatal("Key1 should exist") + } + key1, created, err := ks.Key(tag1, password1, crypto.EDGSecp256_K1) + if err != nil { + t.Fatal(err) + } + if created { + t.Fatal("Key1 should not be created") + } + + si2 := mock.NewFromKeystore(ks, tag2, password2, mockKeyFunc) + exists, err = ks.Exists(tag2) + if err != nil { + t.Fatal(err) + } + if !exists { + t.Fatal("Key2 should exist") + } + key2, created, err := ks.Key(tag2, password2, crypto.EDGSecp256_K1) + if err != nil { + t.Fatal(err) + } + if created { + t.Fatal("Key2 should not be created") + } + + nonces := make([][]byte, 1) + if _, err := io.ReadFull(rand.Reader, nonces[0]); err != nil { + t.Fatal(err) + } + + keys1, err := si1.Key(&key2.PublicKey, nonces) + if err != nil { + t.Fatal(err) + } + keys2, err := si2.Key(&key1.PublicKey, nonces) + if err != nil { + t.Fatal(err) + } + + if !bytes.Equal(keys1[0], keys2[0]) { + t.Fatalf("shared secrets do not match %s, %s", hex.EncodeToString(keys1[0]), hex.EncodeToString(keys2[0])) + } +} From 087bff668d0b6b13dd40f2eb4948d7e883ec3d86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferenc=20S=C3=A1rai?= Date: Tue, 19 Mar 2024 15:50:12 +0100 Subject: [PATCH 09/34] Act swarm address (#15) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * (refactor): ACT interface params + add doc comments * Revert "(refactor): ACT interface params + add doc comments" This reverts commit ee8da04fe7468a4fa65bd390fa17f72f2e93d301. * (refactor): ACT interface params + add doc comments * (refactor): Add error to ACT interface methods * Add in-memory storage and implement Store and Load methods * Move and refactor ACT diffieHellman to Session. Add Key and NewFromKeystore functions. --------- Co-authored-by: Ferenc Sárai Co-authored-by: Bálint Ujvári --- pkg/dynamicaccess/act.go | 68 ++++++++++++++++++++++------ pkg/dynamicaccess/act_test.go | 83 +++-------------------------------- pkg/dynamicaccess/mock/act.go | 19 ++++---- 3 files changed, 70 insertions(+), 100 deletions(-) diff --git a/pkg/dynamicaccess/act.go b/pkg/dynamicaccess/act.go index 07728ebca67..c8cbd9fa663 100644 --- a/pkg/dynamicaccess/act.go +++ b/pkg/dynamicaccess/act.go @@ -5,12 +5,44 @@ package dynamicaccess import ( + "crypto/rand" "encoding/hex" + "fmt" + "sync" "github.com/ethersphere/bee/pkg/manifest" "github.com/ethersphere/bee/pkg/swarm" ) +var lock = &sync.Mutex{} + +type single struct { + memoryMock map[string]manifest.Entry +} + +var singleInMemorySwarm *single + +func getInMemorySwarm() *single { + if singleInMemorySwarm == nil { + lock.Lock() + defer lock.Unlock() + if singleInMemorySwarm == nil { + singleInMemorySwarm = &single{ + memoryMock: make(map[string]manifest.Entry)} + } + } + return singleInMemorySwarm +} + +func getMemory() map[string]manifest.Entry { + ch := make(chan *single) + go func() { + ch <- getInMemorySwarm() + }() + mem := <-ch + return mem.memoryMock +} + // Act represents an interface for accessing and manipulating data. type Act interface { // Add adds a key-value pair to the data store. @@ -19,15 +51,16 @@ type Act interface { // Lookup retrieves the value associated with the given key from the data store. Lookup(key []byte) ([]byte, error) - // Load retrieves the manifest entry associated with the given key from the data store. - Load(key []byte) (manifest.Entry, error) + // Load loads the data store from the given address. + Load(addr swarm.Address) error - // Store stores the given manifest entry in the data store. - Store(me manifest.Entry) error + // Store stores the current state of the data store and returns the address of the ACT. + Store() (swarm.Address, error) } var _ Act = (*defaultAct)(nil) +// defaultAct is a simple implementation of the Act interface, with in memory storage. type defaultAct struct { container map[string]string } @@ -48,20 +81,29 @@ func (act *defaultAct) Lookup(key []byte) ([]byte, error) { return make([]byte, 0), nil } -// to manifestEntry -func (act *defaultAct) Load(key []byte) (manifest.Entry, error) { - return manifest.NewEntry(swarm.NewAddress(key), act.container), nil -} - -// from manifestEntry -func (act *defaultAct) Store(me manifest.Entry) error { - if act.container == nil { - act.container = make(map[string]string) +func (act *defaultAct) Load(addr swarm.Address) error { + memory := getMemory() + me := memory[addr.String()] + if me == nil { + return fmt.Errorf("ACT not found at address: %s", addr.String()) } act.container = me.Metadata() return nil } +func (act *defaultAct) Store() (swarm.Address, error) { + // Generate a random swarm.Address + b := make([]byte, 32) + if _, err := rand.Read(b); err != nil { + return swarm.EmptyAddress, fmt.Errorf("failed to generate random address: %w", err) + } + swarm_ref := swarm.NewAddress(b) + mem := getMemory() + mem[swarm_ref.String()] = manifest.NewEntry(swarm_ref, act.container) + + return swarm_ref, nil +} + func NewDefaultAct() Act { return &defaultAct{ container: make(map[string]string), diff --git a/pkg/dynamicaccess/act_test.go b/pkg/dynamicaccess/act_test.go index 0bd807cbcc2..5a980977f9b 100644 --- a/pkg/dynamicaccess/act_test.go +++ b/pkg/dynamicaccess/act_test.go @@ -6,18 +6,10 @@ package dynamicaccess_test import ( "bytes" - "context" "encoding/hex" "testing" "github.com/ethersphere/bee/pkg/dynamicaccess" - "github.com/ethersphere/bee/pkg/file/loadsave" - "github.com/ethersphere/bee/pkg/file/pipeline" - "github.com/ethersphere/bee/pkg/file/pipeline/builder" - "github.com/ethersphere/bee/pkg/file/redundancy" - "github.com/ethersphere/bee/pkg/manifest" - "github.com/ethersphere/bee/pkg/storage" - mockstorer "github.com/ethersphere/bee/pkg/storer/mock" "github.com/ethersphere/bee/pkg/swarm" ) @@ -36,88 +28,25 @@ func TestActAddLookup(t *testing.T) { } } -func TestActWithManifest(t *testing.T) { - - storer := mockstorer.New() - encrypt := false - ctx := context.Background() - ls := loadsave.New(storer.ChunkStore(), storer.Cache(), pipelineFactory(storer.Cache(), false, 0)) - rootManifest, err := manifest.NewDefaultManifest(ls, encrypt) - if err != nil { - t.Error("DefaultManifest should not return an error") - } +func TestActStoreLoad(t *testing.T) { act := dynamicaccess.NewDefaultAct() lookupKey := swarm.RandAddress(t).Bytes() encryptedAccesskey := swarm.RandAddress(t).Bytes() - err = act.Add(lookupKey, encryptedAccesskey) + err := act.Add(lookupKey, encryptedAccesskey) if err != nil { t.Error("Add() should not return an error") } - actManifEntry, _ := act.Load(lookupKey) - if actManifEntry == nil { - t.Error("Load() should return a manifest.Entry") - } - - err = rootManifest.Add(ctx, hex.EncodeToString(lookupKey), actManifEntry) - if err != nil { - t.Error("rootManifest.Add() should not return an error") - } - - _, err = rootManifest.Store(ctx) + swarm_ref, err := act.Store() if err != nil { - t.Error("rootManifest.Store() should not return an error") - } - - actualMe, err := rootManifest.Lookup(ctx, hex.EncodeToString(lookupKey)) - if err != nil { - t.Error("rootManifest.Lookup() should not return an error") + t.Error("Store() should not return an error") } actualAct := dynamicaccess.NewDefaultAct() - actualAct.Store(actualMe) + actualAct.Load(swarm_ref) actualEak, _ := actualAct.Lookup(lookupKey) if !bytes.Equal(actualEak, encryptedAccesskey) { - t.Errorf("actualAct.Store() value is not the expected %s != %s", actualEak, encryptedAccesskey) - } -} - -func TestActStore(t *testing.T) { - mp := make(map[string]string) - - lookupKey := swarm.RandAddress(t).Bytes() - encryptedAccesskey := swarm.RandAddress(t).Bytes() - mp[hex.EncodeToString(lookupKey)] = hex.EncodeToString(encryptedAccesskey) - - me := manifest.NewEntry(swarm.NewAddress(lookupKey), mp) - act := dynamicaccess.NewDefaultAct() - act.Store(me) - eak, _ := act.Lookup(lookupKey) - - if !bytes.Equal(eak, encryptedAccesskey) { - t.Errorf("Store() value is not the expected %s != %s", eak, encryptedAccesskey) - } - -} - -func TestActLoad(t *testing.T) { - act := dynamicaccess.NewDefaultAct() - lookupKey := swarm.RandAddress(t).Bytes() - encryptedAccesskey := swarm.RandAddress(t).Bytes() - act.Add(lookupKey, encryptedAccesskey) - me, _ := act.Load(lookupKey) - - eak := me.Metadata()[hex.EncodeToString(lookupKey)] - - if eak != hex.EncodeToString(encryptedAccesskey) { - t.Errorf("Load() value is not the expected %s != %s", eak, encryptedAccesskey) - } - -} - -func pipelineFactory(s storage.Putter, encrypt bool, rLevel redundancy.Level) func() pipeline.Interface { - return func() pipeline.Interface { - return builder.NewPipelineBuilder(context.Background(), s, encrypt, rLevel) + t.Errorf("actualAct.Load() value is not the expected %s != %s", hex.EncodeToString(actualEak), hex.EncodeToString(encryptedAccesskey)) } } diff --git a/pkg/dynamicaccess/mock/act.go b/pkg/dynamicaccess/mock/act.go index 4f320c13dce..a915f38f123 100644 --- a/pkg/dynamicaccess/mock/act.go +++ b/pkg/dynamicaccess/mock/act.go @@ -6,14 +6,14 @@ package mock import ( "github.com/ethersphere/bee/pkg/dynamicaccess" - "github.com/ethersphere/bee/pkg/manifest" + "github.com/ethersphere/bee/pkg/swarm" ) type ActMock struct { AddFunc func(key []byte, val []byte) error LookupFunc func(key []byte) ([]byte, error) - LoadFunc func(key []byte) (manifest.Entry, error) - StoreFunc func(me manifest.Entry) error + LoadFunc func(addr swarm.Address) error + StoreFunc func() (swarm.Address, error) } var _ dynamicaccess.Act = (*ActMock)(nil) @@ -32,19 +32,18 @@ func (act *ActMock) Lookup(key []byte) ([]byte, error) { return act.LookupFunc(key) } -func (act *ActMock) Load(key []byte) (manifest.Entry, error) { +func (act *ActMock) Load(addr swarm.Address) error { if act.LoadFunc == nil { - return nil, nil + return nil } - return act.LoadFunc(key) + return act.LoadFunc(addr) } -func (act *ActMock) Store(me manifest.Entry) error { +func (act *ActMock) Store() (swarm.Address, error) { if act.StoreFunc == nil { - return nil + return swarm.EmptyAddress, nil } - act.StoreFunc(me) - return nil + return act.StoreFunc() } func NewActMock(addFunc func(key []byte, val []byte) error, getFunc func(key []byte) ([]byte, error)) dynamicaccess.Act { From bee534c5e5b0eb89fdd7d3dc3f6295ad0f82f2e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferenc=20S=C3=A1rai?= Date: Tue, 19 Mar 2024 17:00:52 +0100 Subject: [PATCH 10/34] (rename): defaultAct to inMemoryAct (#17) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * (refactor): ACT interface params + add doc comments * Revert "(refactor): ACT interface params + add doc comments" This reverts commit ee8da04fe7468a4fa65bd390fa17f72f2e93d301. * (refactor): ACT interface params + add doc comments * (refactor): Add error to ACT interface methods * Add in-memory storage and implement Store and Load methods * *refactor) Rename defaultAct to inMemroryAct --------- Co-authored-by: Ferenc Sárai --- pkg/dynamicaccess/act.go | 18 +++++++++--------- pkg/dynamicaccess/act_test.go | 6 +++--- pkg/dynamicaccess/controller.go | 2 +- pkg/dynamicaccess/grantee_manager_test.go | 2 +- pkg/dynamicaccess/history_test.go | 6 +++--- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/pkg/dynamicaccess/act.go b/pkg/dynamicaccess/act.go index c8cbd9fa663..1153f2fc868 100644 --- a/pkg/dynamicaccess/act.go +++ b/pkg/dynamicaccess/act.go @@ -58,19 +58,19 @@ type Act interface { Store() (swarm.Address, error) } -var _ Act = (*defaultAct)(nil) +var _ Act = (*inMemoryAct)(nil) -// defaultAct is a simple implementation of the Act interface, with in memory storage. -type defaultAct struct { +// inMemoryAct is a simple implementation of the Act interface, with in memory storage. +type inMemoryAct struct { container map[string]string } -func (act *defaultAct) Add(key []byte, val []byte) error { +func (act *inMemoryAct) Add(key []byte, val []byte) error { act.container[hex.EncodeToString(key)] = hex.EncodeToString(val) return nil } -func (act *defaultAct) Lookup(key []byte) ([]byte, error) { +func (act *inMemoryAct) Lookup(key []byte) ([]byte, error) { if key, ok := act.container[hex.EncodeToString(key)]; ok { bytes, err := hex.DecodeString(key) if err != nil { @@ -81,7 +81,7 @@ func (act *defaultAct) Lookup(key []byte) ([]byte, error) { return make([]byte, 0), nil } -func (act *defaultAct) Load(addr swarm.Address) error { +func (act *inMemoryAct) Load(addr swarm.Address) error { memory := getMemory() me := memory[addr.String()] if me == nil { @@ -91,7 +91,7 @@ func (act *defaultAct) Load(addr swarm.Address) error { return nil } -func (act *defaultAct) Store() (swarm.Address, error) { +func (act *inMemoryAct) Store() (swarm.Address, error) { // Generate a random swarm.Address b := make([]byte, 32) if _, err := rand.Read(b); err != nil { @@ -104,8 +104,8 @@ func (act *defaultAct) Store() (swarm.Address, error) { return swarm_ref, nil } -func NewDefaultAct() Act { - return &defaultAct{ +func NewInMemoryAct() Act { + return &inMemoryAct{ container: make(map[string]string), } } diff --git a/pkg/dynamicaccess/act_test.go b/pkg/dynamicaccess/act_test.go index 5a980977f9b..3480077102a 100644 --- a/pkg/dynamicaccess/act_test.go +++ b/pkg/dynamicaccess/act_test.go @@ -14,7 +14,7 @@ import ( ) func TestActAddLookup(t *testing.T) { - act := dynamicaccess.NewDefaultAct() + act := dynamicaccess.NewInMemoryAct() lookupKey := swarm.RandAddress(t).Bytes() encryptedAccesskey := swarm.RandAddress(t).Bytes() err := act.Add(lookupKey, encryptedAccesskey) @@ -30,7 +30,7 @@ func TestActAddLookup(t *testing.T) { func TestActStoreLoad(t *testing.T) { - act := dynamicaccess.NewDefaultAct() + act := dynamicaccess.NewInMemoryAct() lookupKey := swarm.RandAddress(t).Bytes() encryptedAccesskey := swarm.RandAddress(t).Bytes() err := act.Add(lookupKey, encryptedAccesskey) @@ -43,7 +43,7 @@ func TestActStoreLoad(t *testing.T) { t.Error("Store() should not return an error") } - actualAct := dynamicaccess.NewDefaultAct() + actualAct := dynamicaccess.NewInMemoryAct() actualAct.Load(swarm_ref) actualEak, _ := actualAct.Lookup(lookupKey) if !bytes.Equal(actualEak, encryptedAccesskey) { diff --git a/pkg/dynamicaccess/controller.go b/pkg/dynamicaccess/controller.go index 1637234d9c2..6267a878279 100644 --- a/pkg/dynamicaccess/controller.go +++ b/pkg/dynamicaccess/controller.go @@ -30,7 +30,7 @@ func (c *defaultController) UploadHandler(ref swarm.Address, publisher *ecdsa.Pu act, _ := c.history.Lookup(0) if act == nil { // new feed - act = NewDefaultAct() + act = NewInMemoryAct() act = c.granteeManager.Publish(act, *publisher, topic) } //FIXME: check if ACT is consistent with the grantee list diff --git a/pkg/dynamicaccess/grantee_manager_test.go b/pkg/dynamicaccess/grantee_manager_test.go index 4b55400f465..bc90ef504ae 100644 --- a/pkg/dynamicaccess/grantee_manager_test.go +++ b/pkg/dynamicaccess/grantee_manager_test.go @@ -22,7 +22,7 @@ func setupAccessLogic(privateKey *ecdsa.PrivateKey) dynamicaccess.AccessLogic { } func TestAdd(t *testing.T) { - act := dynamicaccess.NewDefaultAct() + act := dynamicaccess.NewInMemoryAct() m := dynamicaccess.NewGranteeManager(setupAccessLogic(getPrivateKey())) pub, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) diff --git a/pkg/dynamicaccess/history_test.go b/pkg/dynamicaccess/history_test.go index f7997d55fcd..a58aa44ecaa 100644 --- a/pkg/dynamicaccess/history_test.go +++ b/pkg/dynamicaccess/history_test.go @@ -42,9 +42,9 @@ func prepareTestHistory() dynamicaccess.History { var ( h = mock.NewHistory() now = time.Now() - act1 = dynamicaccess.NewDefaultAct() - act2 = dynamicaccess.NewDefaultAct() - act3 = dynamicaccess.NewDefaultAct() + act1 = dynamicaccess.NewInMemoryAct() + act2 = dynamicaccess.NewInMemoryAct() + act3 = dynamicaccess.NewInMemoryAct() ) act1.Add([]byte("key1"), []byte("value1")) act2.Add([]byte("key1"), []byte("value2")) From fb7562a8089a7837630d6ec1b273031811d55dc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferenc=20S=C3=A1rai?= Date: Wed, 20 Mar 2024 10:12:43 +0100 Subject: [PATCH 11/34] (refactor): Update controller_test.go to use NewInMemoryAct, modify Session.Key to return correct dimensional byte slice (#18) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * (refactor): Update controller_test.go to use NewInMemoryAct, modify Session.Key to return two-dimensional byte slice * (refactor:) Refactor session Key function to use append instead of index-based assignment --------- Co-authored-by: Ferenc Sárai --- pkg/dynamicaccess/controller_test.go | 18 ++++++++---------- pkg/dynamicaccess/session.go | 2 +- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/pkg/dynamicaccess/controller_test.go b/pkg/dynamicaccess/controller_test.go index 30e17cde542..531470eceee 100644 --- a/pkg/dynamicaccess/controller_test.go +++ b/pkg/dynamicaccess/controller_test.go @@ -22,11 +22,9 @@ func mockTestHistory(key, val []byte) dynamicaccess.History { var ( h = mock.NewHistory() now = time.Now() - act = mock.NewActMock(nil, func(lookupKey []byte) ([]byte, error) { - return val, nil - }) + act = dynamicaccess.NewInMemoryAct() ) - // act.Add(key, val) + act.Add(key, val) h.Insert(now.AddDate(-3, 0, 0).Unix(), act) return h } @@ -36,11 +34,11 @@ func TestDecrypt(t *testing.T) { ak := encryption.Key([]byte("cica")) si := dynamicaccess.NewDefaultSession(pk) - aek, _ := si.Key(&pk.PublicKey, [][]byte{{1}}) - e2 := encryption.New(aek[0], 0, uint32(0), hashFunc) + aek, _ := si.Key(&pk.PublicKey, [][]byte{{0}, {1}}) + e2 := encryption.New(aek[1], 0, uint32(0), hashFunc) peak, _ := e2.Encrypt(ak) - h := mockTestHistory(nil, peak) + h := mockTestHistory(aek[0], peak) al := setupAccessLogic(pk) gm := dynamicaccess.NewGranteeManager(al) c := dynamicaccess.NewController(h, gm, al) @@ -62,11 +60,11 @@ func TestEncrypt(t *testing.T) { ak := encryption.Key([]byte("cica")) si := dynamicaccess.NewDefaultSession(pk) - aek, _ := si.Key(&pk.PublicKey, [][]byte{{1}}) - e2 := encryption.New(aek[0], 0, uint32(0), hashFunc) + aek, _ := si.Key(&pk.PublicKey, [][]byte{{0}, {1}}) + e2 := encryption.New(aek[1], 0, uint32(0), hashFunc) peak, _ := e2.Encrypt(ak) - h := mockTestHistory(nil, peak) + h := mockTestHistory(aek[0], peak) al := setupAccessLogic(pk) gm := dynamicaccess.NewGranteeManager(al) c := dynamicaccess.NewController(h, gm, al) diff --git a/pkg/dynamicaccess/session.go b/pkg/dynamicaccess/session.go index 95a3c07e87b..9d7634ffd01 100644 --- a/pkg/dynamicaccess/session.go +++ b/pkg/dynamicaccess/session.go @@ -26,7 +26,7 @@ func (s *session) Key(publicKey *ecdsa.PublicKey, nonces [][]byte) ([][]byte, er return nil, errors.New("shared secret is point at infinity") } - keys := make([][]byte, len(nonces)) + keys := make([][]byte, 0, len(nonces)) for _, nonce := range nonces { key, err := crypto.LegacyKeccak256(append(x.Bytes(), nonce...)) if err != nil { From bccfacc82cd0a25f687a2716baa1db611ec8ccf1 Mon Sep 17 00:00:00 2001 From: rolandlor <33499567+rolandlor@users.noreply.github.com> Date: Thu, 21 Mar 2024 13:58:43 +0100 Subject: [PATCH 12/34] Act access logic merge (#19) * grantee container and access logc tests are passed * refactored access logic and grantee container * PR 19 comments resolving * Refactor * Refactor --- pkg/dynamicaccess/accesslogic.go | 153 ++++---- pkg/dynamicaccess/accesslogic_test.go | 447 ++++++++++------------ pkg/dynamicaccess/act.go | 2 +- pkg/dynamicaccess/controller.go | 10 +- pkg/dynamicaccess/grantee.go | 34 +- pkg/dynamicaccess/grantee_manager.go | 12 +- pkg/dynamicaccess/grantee_manager_test.go | 6 +- pkg/dynamicaccess/grantee_test.go | 115 +++--- 8 files changed, 375 insertions(+), 404 deletions(-) diff --git a/pkg/dynamicaccess/accesslogic.go b/pkg/dynamicaccess/accesslogic.go index 8f46256cd85..417a4a40878 100644 --- a/pkg/dynamicaccess/accesslogic.go +++ b/pkg/dynamicaccess/accesslogic.go @@ -2,6 +2,7 @@ package dynamicaccess import ( "crypto/ecdsa" + "fmt" encryption "github.com/ethersphere/bee/pkg/encryption" "github.com/ethersphere/bee/pkg/swarm" @@ -10,146 +11,154 @@ import ( var hashFunc = sha3.NewLegacyKeccak256 -type AccessLogic interface { - Get(act Act, encryped_ref swarm.Address, publisher ecdsa.PublicKey, tag string) (swarm.Address, error) - EncryptRef(act Act, publisherPubKey ecdsa.PublicKey, ref swarm.Address) (swarm.Address, error) - //Add(act *Act, ref string, publisher ecdsa.PublicKey, tag string) (string, error) - getKeys(publicKey ecdsa.PublicKey) ([][]byte, error) - getEncryptedAccessKey(act Act, lookup_key []byte) ([]byte, error) - //createEncryptedAccessKey(ref string) - Add_New_Grantee_To_Content(act Act, publisherPubKey, granteePubKey ecdsa.PublicKey) (Act, error) - AddPublisher(act Act, publisher ecdsa.PublicKey, tag string) (Act, error) - // CreateAccessKey() +// Logic has the responsibility to return a ref for a given grantee and create new encrypted reference for a grantee +type Logic interface { + // Adds a new grantee to the ACT + AddNewGranteeToContent(act Act, publisherPubKey, granteePubKey *ecdsa.PublicKey) (Act, error) + // Get will return a decrypted reference, for given encrypted reference and grantee !!!!!!!!!!!!!!!!!!!!! + Get(act Act, encryped_ref swarm.Address, publisher *ecdsa.PublicKey) (swarm.Address, error) } -type DefaultAccessLogic struct { +type ActLogic struct { session Session - //encryption encryption.Interface } -// Will create a new Act list with only one element (the creator), and will also create encrypted_ref -func (al *DefaultAccessLogic) AddPublisher(act Act, publisher ecdsa.PublicKey, tag string) (Act, error) { - access_key := encryption.GenerateRandomKey(encryption.KeyLength) +var _ Logic = (*ActLogic)(nil) + +// Adds a new publisher to an empty act +func (al ActLogic) AddPublisher(act Act, publisher *ecdsa.PublicKey) (Act, error) { + accessKey := encryption.GenerateRandomKey(encryption.KeyLength) keys, err := al.getKeys(publisher) if err != nil { return nil, err } - lookup_key := keys[0] - access_key_encryption_key := keys[1] + lookupKey := keys[0] + accessKeyEncryptionKey := keys[1] - access_key_cipher := encryption.New(encryption.Key(access_key_encryption_key), 0, uint32(0), hashFunc) - encrypted_access_key, _ := access_key_cipher.Encrypt(access_key) + accessKeyCipher := encryption.New(encryption.Key(accessKeyEncryptionKey), 0, uint32(0), hashFunc) + encryptedAccessKey, err := accessKeyCipher.Encrypt([]byte(accessKey)) + if err != nil { + return nil, err + } - act.Add(lookup_key, encrypted_access_key) + act.Add(lookupKey, encryptedAccessKey) return act, nil } -func (al *DefaultAccessLogic) EncryptRef(act Act, publisherPubKey ecdsa.PublicKey, ref swarm.Address) (swarm.Address, error) { - access_key := al.getAccessKey(act, publisherPubKey) - ref_cipher := encryption.New(access_key, 0, uint32(0), hashFunc) - encrypted_ref, _ := ref_cipher.Encrypt(ref.Bytes()) - return swarm.NewAddress(encrypted_ref), nil -} - -// publisher is public key -func (al *DefaultAccessLogic) Add_New_Grantee_To_Content(act Act, publisherPubKey, granteePubKey ecdsa.PublicKey) (Act, error) { +// Encrypts a SWARM reference for a publisher +func (al ActLogic) EncryptRef(act Act, publisherPubKey *ecdsa.PublicKey, ref swarm.Address) (swarm.Address, error) { + accessKey := al.getAccessKey(act, publisherPubKey) + refCipher := encryption.New(accessKey, 0, uint32(0), hashFunc) + encryptedRef, _ := refCipher.Encrypt(ref.Bytes()) - // error handling no encrypted_ref + return swarm.NewAddress(encryptedRef), nil +} - // 2 Diffie-Hellman for the publisher (the Creator) +// Adds a new grantee to the ACT +func (al ActLogic) AddNewGranteeToContent(act Act, publisherPubKey, granteePubKey *ecdsa.PublicKey) (Act, error) { // Get previously generated access key - access_key := al.getAccessKey(act, publisherPubKey) - - // --Encrypt access key for new Grantee-- + accessKey := al.getAccessKey(act, publisherPubKey) - // 2 Diffie-Hellman for the Grantee + // Encrypt the access key for the new Grantee keys, err := al.getKeys(granteePubKey) if err != nil { return nil, err } - lookup_key := keys[0] - access_key_encryption_key := keys[1] + lookupKey := keys[0] + accessKeyEncryptionKey := keys[1] // Encrypt the access key for the new Grantee - cipher := encryption.New(encryption.Key(access_key_encryption_key), 0, uint32(0), hashFunc) - granteeEncryptedAccessKey, _ := cipher.Encrypt(access_key) + cipher := encryption.New(encryption.Key(accessKeyEncryptionKey), 0, uint32(0), hashFunc) + granteeEncryptedAccessKey, err := cipher.Encrypt(accessKey) + if err != nil { + return nil, err + } + // Add the new encrypted access key for the Act - act.Add(lookup_key, granteeEncryptedAccessKey) + act.Add(lookupKey, granteeEncryptedAccessKey) return act, nil } -func (al *DefaultAccessLogic) getAccessKey(act Act, publisherPubKey ecdsa.PublicKey) []byte { +// Will return the access key for a publisher (public key) +func (al *ActLogic) getAccessKey(act Act, publisherPubKey *ecdsa.PublicKey) []byte { keys, err := al.getKeys(publisherPubKey) if err != nil { return nil } - publisher_lookup_key := keys[0] - publisher_ak_decryption_key := keys[1] - - access_key_decryption_cipher := encryption.New(encryption.Key(publisher_ak_decryption_key), 0, uint32(0), hashFunc) - encrypted_ak, _ := al.getEncryptedAccessKey(act, publisher_lookup_key) - access_key, _ := access_key_decryption_cipher.Decrypt(encrypted_ak) - return access_key -} + publisherLookupKey := keys[0] + publisherAKDecryptionKey := keys[1] -// -// act[lookupKey] := valamilyen_cipher.Encrypt(access_key) + accessKeyDecryptionCipher := encryption.New(encryption.Key(publisherAKDecryptionKey), 0, uint32(0), hashFunc) + encryptedAK, err := al.getEncryptedAccessKey(act, publisherLookupKey) + if err != nil { + return nil + } -// end of pseudo code like code + accessKey, err := accessKeyDecryptionCipher.Decrypt(encryptedAK) + if err != nil { + return nil + } -// func (al *DefaultAccessLogic) CreateAccessKey(reference string) { -// } + return accessKey +} -func (al *DefaultAccessLogic) getKeys(publicKey ecdsa.PublicKey) ([][]byte, error) { +func (al *ActLogic) getKeys(publicKey *ecdsa.PublicKey) ([][]byte, error) { // Generate lookup key and access key decryption oneByteArray := []byte{1} zeroByteArray := []byte{0} - keys, err := al.session.Key(&publicKey, [][]byte{zeroByteArray, oneByteArray}) + keys, err := al.session.Key(publicKey, [][]byte{zeroByteArray, oneByteArray}) if err != nil { - return [][]byte{}, err + return nil, err } return keys, nil } -func (al *DefaultAccessLogic) getEncryptedAccessKey(act Act, lookup_key []byte) ([]byte, error) { +// Gets the encrypted access key for a given grantee +func (al *ActLogic) getEncryptedAccessKey(act Act, lookup_key []byte) ([]byte, error) { val, err := act.Lookup(lookup_key) if err != nil { - return []byte{}, err + return nil, err } return val, nil } -func (al *DefaultAccessLogic) Get(act Act, encryped_ref swarm.Address, publisher ecdsa.PublicKey, tag string) (swarm.Address, error) { +// Get will return a decrypted reference, for given encrypted reference and grantee +func (al ActLogic) Get(act Act, encryped_ref swarm.Address, grantee *ecdsa.PublicKey) (swarm.Address, error) { + if encryped_ref.Compare(swarm.EmptyAddress) == 0 { + return swarm.EmptyAddress, fmt.Errorf("encrypted ref not provided") + } + if grantee == nil { + return swarm.EmptyAddress, fmt.Errorf("grantee not provided") + } - keys, err := al.getKeys(publisher) + keys, err := al.getKeys(grantee) if err != nil { return swarm.EmptyAddress, err } - lookup_key := keys[0] - access_key_decryption_key := keys[1] + lookupKey := keys[0] + accessKeyDecryptionKey := keys[1] // Lookup encrypted access key from the ACT manifest - - encrypted_access_key, err := al.getEncryptedAccessKey(act, lookup_key) + encryptedAccessKey, err := al.getEncryptedAccessKey(act, lookupKey) if err != nil { return swarm.EmptyAddress, err } // Decrypt access key - access_key_cipher := encryption.New(encryption.Key(access_key_decryption_key), 0, uint32(0), hashFunc) - access_key, err := access_key_cipher.Decrypt(encrypted_access_key) + accessKeyCipher := encryption.New(encryption.Key(accessKeyDecryptionKey), 0, uint32(0), hashFunc) + accessKey, err := accessKeyCipher.Decrypt(encryptedAccessKey) if err != nil { return swarm.EmptyAddress, err } // Decrypt reference - ref_cipher := encryption.New(access_key, 0, uint32(0), hashFunc) - ref, err := ref_cipher.Decrypt(encryped_ref.Bytes()) + refCipher := encryption.New(accessKey, 0, uint32(0), hashFunc) + ref, err := refCipher.Decrypt(encryped_ref.Bytes()) if err != nil { return swarm.EmptyAddress, err } @@ -157,8 +166,8 @@ func (al *DefaultAccessLogic) Get(act Act, encryped_ref swarm.Address, publisher return swarm.NewAddress(ref), nil } -func NewAccessLogic(s Session) AccessLogic { - return &DefaultAccessLogic{ +func NewLogic(s Session) ActLogic { + return ActLogic{ session: s, } -} +} \ No newline at end of file diff --git a/pkg/dynamicaccess/accesslogic_test.go b/pkg/dynamicaccess/accesslogic_test.go index c3125b65cd6..46a4cc39ec6 100644 --- a/pkg/dynamicaccess/accesslogic_test.go +++ b/pkg/dynamicaccess/accesslogic_test.go @@ -1,237 +1,214 @@ package dynamicaccess_test -// import ( -// "crypto/ecdsa" -// "crypto/elliptic" -// "crypto/rand" -// "errors" -// "fmt" -// "testing" - -// "github.com/ethersphere/bee/pkg/crypto" -// "github.com/ethersphere/bee/pkg/swarm" -// ) - -// func setupAccessLogic() AccessLogic { -// privateKey, err := crypto.GenerateSecp256k1Key() -// if err != nil { -// errors.New("error creating private key") -// } -// diffieHellman := NewDiffieHellman(privateKey) -// al := NewAccessLogic(diffieHellman) - -// return al -// } - -// func TestGetLookupKey_Success(t *testing.T) { -// al := setupAccessLogic() - -// id0, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) -// // ! this will be random, we can not know the lookup key for a randomly generated key -// act, encryptedRef, _ := al.ActInit(swarm.NewAddress([]byte("42")), id0.PublicKey, "") -// fmt.Println(act, encryptedRef) - -// tag := "exampleTag" - -// lookupKey, err := al.getLookUpKey(id0.PublicKey, tag) -// if err != nil { -// t.Errorf("Could not fetch lookup key from publisher and tag") -// } - -// expectedLookupKey := "expectedLookupKey" -// if lookupKey != expectedLookupKey { -// fmt.Println(string(lookupKey)) -// t.Errorf("The lookup key that was returned is not correct!") -// } -// } - -// func TestGetLookUpKey_Error(t *testing.T) { -// al := setupAccessLogic() - -// invalidPublisher := ecdsa.PublicKey{} -// tag := "exampleTag" - -// lookupKey, err := al.getLookUpKey(invalidPublisher, tag) - -// if err != nil { -// t.Errorf("There was an error while fetching lookup key") -// } - -// if lookupKey != "" { -// t.Errorf("Expected lookup key to be empty for invalid input") -// } -// } - -// func TestGetAccessKeyDecriptionKey_Success(t *testing.T) { -// al := setupAccessLogic() - -// id0, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) -// tag := "exampleTag" - -// access_key_decryption_key, err := al.getAccessKeyDecriptionKey(id0.PublicKey, tag) -// if err != nil { -// t.Errorf("GetAccessKeyDecriptionKey gave back error") -// } - -// expectedResult := "we-dont-know" -// if access_key_decryption_key != expectedResult { -// t.Errorf("The access key decryption key is not correct!") -// } -// } - -// func TestGetAccessKeyDecriptionKey_Error(t *testing.T) { -// al := setupAccessLogic() - -// invalidPublisher := ecdsa.PublicKey{} -// tag := "exampleTag" - -// access_key_decryption_key, err := al.getAccessKeyDecriptionKey(invalidPublisher, tag) -// if err != nil { -// t.Errorf("GetAccessKeyDecriptionKey gave back error") -// } - -// if access_key_decryption_key != "" { -// t.Errorf("GetAccessKeyDecriptionKey should give back empty string for invalid input!") -// } -// } - -// func TestGetEncryptedAccessKey_Success(t *testing.T) { -// al := setupAccessLogic() - -// lookupKey := "exampleLookupKey" -// id0, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - -// act, _, _ := al.ActInit(swarm.NewAddress([]byte("42")), id0.PublicKey, "") - -// encrypted_access_key, err := al.getEncryptedAccessKey(*act, lookupKey) -// if err != nil { -// t.Errorf("There was an error while executing GetEncryptedAccessKey") -// } - -// expectedEncryptedKey := "abc013encryptedkey" -// if encrypted_access_key.Reference().String() != expectedEncryptedKey { -// t.Errorf("GetEncryptedAccessKey didn't give back the expected value!") -// } -// } - -// func TestGetEncryptedAccessKey_Error(t *testing.T) { -// al := setupAccessLogic() - -// lookupKey := "exampleLookupKey" -// id0, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - -// act, _, _ := al.ActInit(swarm.NewAddress([]byte("42")), id0.PublicKey, "") -// empty_act_result, _ := al.getEncryptedAccessKey(*act, lookupKey) -// if empty_act_result != nil { -// t.Errorf("GetEncryptedAccessKey should give back nil for empty act root hash!") -// } - -// empty_lookup_result, _ := al.getEncryptedAccessKey(*act, "") - -// if empty_lookup_result != nil { -// t.Errorf("GetEncryptedAccessKey should give back nil for empty lookup key!") -// } -// } - -// func TestGet_Success(t *testing.T) { -// al := setupAccessLogic() - -// id0, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) -// act, encryptedRef, _ := al.ActInit(swarm.NewAddress([]byte("42")), id0.PublicKey, "") -// tag := "exampleTag" - -// ref, err := al.Get(act, encryptedRef, id0.PublicKey, tag) -// if err != nil { -// t.Errorf("There was an error while calling Get") -// } - -// expectedRef := "bzzNotEncrypted128long" -// if ref != expectedRef { -// t.Errorf("Get gave back wrong Swarm reference!") -// } -// } - -// func TestGet_Error(t *testing.T) { -// al := setupAccessLogic() - -// //actRootHash := "0xabcexample" -// id0, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) -// act, encrypredRef, err := al.ActInit(swarm.NewAddress([]byte("42")), id0.PublicKey, "") -// if err != nil { -// t.Errorf("Error initializing Act") -// t.Errorf(err.Error()) -// } -// //encryptedRef := "bzzabcasab" -// tag := "exampleTag" - -// refOne, err := al.Get(act, encrypredRef, id0.PublicKey, tag) -// if err != nil { -// t.Errorf(err.Error()) -// } -// if refOne != "" { -// t.Errorf("Get should give back empty string if ACT root hash not provided!") -// } - -// refTwo, _ := al.Get(act, swarm.EmptyAddress, id0.PublicKey, tag) -// if refTwo != "" { -// t.Errorf("Get should give back empty string if encrypted ref not provided!") -// } - -// refThree, _ := al.Get(act, encrypredRef, ecdsa.PublicKey{}, tag) -// if refThree != "" { -// t.Errorf("Get should give back empty string if publisher not provided!") -// } - -// refFour, _ := al.Get(act, encrypredRef, id0.PublicKey, "") -// if refFour != "" { -// t.Errorf("Get should give back empty string if tag was not provided!") -// } -// } - -// func TestNewAccessLogic(t *testing.T) { -// logic := setupAccessLogic() - -// _, ok := logic.(*DefaultAccessLogic) -// if !ok { -// t.Errorf("NewAccessLogic: expected type *DefaultAccessLogic, got %T", logic) -// } -// } - -// // func TestAddGrantee(t *testing.T) { -// // al := setupAccessLogic() -// // // ref := "example_ref" -// // id0, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) -// // testGranteeList := NewGrantee() - -// // // Add grantee keys to the testGranteeList -// // id1, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) -// // id2, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) -// // id3, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) -// // testGranteeList.AddGrantees([]ecdsa.PublicKey{id1.PublicKey, id2.PublicKey, id3.PublicKey}) - -// // // Initialize empty ACT -// // actMock := MockAct.NewActMock() -// // actMockRootHash := "exampleRootHash" - -// // // Add each grantee to content using ActMock and validate the resulting ACT -// // for i := 0; i < len(testGranteeList.GetGrantees()); i++ { -// // lookupKey, _ := al.getLookUpKey(testGranteeList.GetGrantees()[i], "") -// // encryptedAccessKey := "exampleEncryptedAccessKey" -// // _, err := actMock.Add(actMockRootHash, []byte(lookupKey), []byte(encryptedAccessKey)) -// // if err != nil { -// // t.Fatalf("Failed to add grantee to content using ActMock: %v", err) -// // } - -// // // Validate the resulting ACT -// // encryptedAccessKeyFromMock, err := actMock.Get(actMockRootHash, []byte(lookupKey)) -// // if err != nil { -// // t.Fatalf("Failed to retrieve encrypted access key from ActMock: %v", err) -// // } -// // encryptedAccessKeyFromMockBytes, _ := hex.DecodeString(encryptedAccessKeyFromMock) -// // if string(encryptedAccessKeyFromMockBytes) != encryptedAccessKey { -// // t.Errorf("Encrypted access key retrieved from ActMock doesn't match expected value") -// // } -// // } - -// // al.Add_New_Grantee_To_Content(actMock, encryptedRef, id0.PublicKey, testGranteeList.GetGrantees()[i]) -// // } +import ( + "crypto/ecdsa" + "crypto/elliptic" + "encoding/hex" + "math/big" + "testing" + + "github.com/ethersphere/bee/pkg/dynamicaccess" + "github.com/ethersphere/bee/pkg/swarm" +) + +// Generates a new test environment with a fix private key +func setupAccessLogic2() dynamicaccess.ActLogic { + privateKey := generateFixPrivateKey(1000) + diffieHellman := dynamicaccess.NewDefaultSession(&privateKey) + al := dynamicaccess.NewLogic(diffieHellman) + + return al +} + +// Generates a fixed identity with private and public key. The private key is generated from the input +func generateFixPrivateKey(input int64) ecdsa.PrivateKey { + fixedD := big.NewInt(input) + curve := elliptic.P256() + x, y := curve.ScalarBaseMult(fixedD.Bytes()) + + privateKey := ecdsa.PrivateKey{ + PublicKey: ecdsa.PublicKey{ + Curve: curve, + X: x, + Y: y, + }, + D: fixedD, + } + + return privateKey +} + +func TestGet_Success(t *testing.T) { + al := setupAccessLogic2() + id0 := generateFixPrivateKey(0) + + act := dynamicaccess.NewInMemoryAct() + act, err := al.AddPublisher(act, &id0.PublicKey) + if err != nil { + t.Errorf("AddPublisher: expected no error, got %v", err) + } + + byteRef, _ := hex.DecodeString("39a5ea87b141fe44aa609c3327ecd896c0e2122897f5f4bbacf74db1033c5559") + + expectedRef := swarm.NewAddress(byteRef) + t.Logf("encryptedRef: %s", expectedRef.String()) + + encryptedRef, err := al.EncryptRef(act, &id0.PublicKey, expectedRef) + t.Logf("encryptedRef: %s", encryptedRef.String()) + if err != nil { + t.Errorf("There was an error while calling EncryptRef: ") + t.Error(err) + } + + ref, err := al.Get(act, encryptedRef, &id0.PublicKey) + if err != nil { + t.Errorf("There was an error while calling Get: ") + t.Error(err) + } + + if expectedRef.Compare(ref) != 0 { + + t.Errorf("Get gave back wrong Swarm reference!") + } +} + +// This test function tests those cases where different parameters are missing +func TestGet_Error(t *testing.T) { + al := setupAccessLogic2() + id0 := generateFixPrivateKey(0) + + act := dynamicaccess.NewInMemoryAct() + act, err := al.AddPublisher(act, &id0.PublicKey) + if err != nil { + t.Errorf("AddPublisher: expected no error, got %v", err) + } + + expectedRef := "39a5ea87b141fe44aa609c3327ecd896c0e2122897f5f4bbacf74db1033c5559" + + encryptedRef, _ := al.EncryptRef(act, &id0.PublicKey, swarm.NewAddress([]byte(expectedRef))) + + _, err = al.Get(dynamicaccess.NewInMemoryAct(), encryptedRef, &id0.PublicKey) + if err == nil { + t.Errorf("Get should give back encrypted access key not found error!") + } + + refTwo, _ := al.Get(act, swarm.EmptyAddress, &id0.PublicKey) + if swarm.EmptyAddress.Compare(refTwo) != 0 { + t.Errorf("Get should give back empty string if encrypted ref not provided!") + } + + _, err = al.Get(act, encryptedRef, nil) + if err == nil { + t.Errorf("Get should give back error if grantee not provided!") + } +} + +func TestAddPublisher(t *testing.T) { + al := setupAccessLogic2() + id0 := generateFixPrivateKey(0) + savedLookupKey := "bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a" + act := dynamicaccess.NewInMemoryAct() + act, err := al.AddPublisher(act, &id0.PublicKey) + if err != nil { + t.Errorf("AddPublisher: expected no error, got %v", err) + } + + decodedSavedLookupKey, err := hex.DecodeString(savedLookupKey) + if err != nil { + t.Errorf("AddPublisher: expected no error, got %v", err) + } + + encryptedAccessKey, _ := act.Lookup(decodedSavedLookupKey) + decodedEncryptedAccessKey := hex.EncodeToString(encryptedAccessKey) + + // A random value is returned so it is only possibly to check the length of the returned value + // We know the lookup key because the generated private key is fixed + if len(decodedEncryptedAccessKey) != 64 { + t.Errorf("AddPublisher: expected encrypted access key length 64, got %d", len(decodedEncryptedAccessKey)) + } + if act == nil { + t.Errorf("AddPublisher: expected act, got nil") + } +} + +func TestAdd_New_Grantee_To_Content(t *testing.T) { + al := setupAccessLogic2() + + id0 := generateFixPrivateKey(0) + id1 := generateFixPrivateKey(1) + id2 := generateFixPrivateKey(2) + + publisherLookupKey := "bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a" + firstAddedGranteeLookupKey := "e221a2abf64357260e8f2c937ee938aed98dce097e537c1a3fd4caf73510dbe4" + secondAddedGranteeLookupKey := "8fe8dff7cd15a6a0095c1b25071a5691e7c901fd0b95857a96c0e4659b48716a" + + act := dynamicaccess.NewInMemoryAct() + act, err := al.AddPublisher(act, &id0.PublicKey) + if err != nil { + t.Errorf("AddNewGrantee: expected no error, got %v", err) + } + + act, err = al.AddNewGranteeToContent(act, &id0.PublicKey, &id1.PublicKey) + if err != nil { + t.Errorf("AddNewGrantee: expected no error, got %v", err) + } + + act, err = al.AddNewGranteeToContent(act, &id0.PublicKey, &id2.PublicKey) + if err != nil { + t.Errorf("AddNewGrantee: expected no error, got %v", err) + } + + lookupKeyAsByte, err := hex.DecodeString(publisherLookupKey) + if err != nil { + t.Errorf("AddNewGrantee: expected no error, got %v", err) + } + result, _ := act.Lookup(lookupKeyAsByte) + hexEncodedEncryptedAK := hex.EncodeToString(result) + if len(hexEncodedEncryptedAK) != 64 { + t.Errorf("AddNewGrantee: expected encrypted access key length 64, got %d", len(hexEncodedEncryptedAK)) + } + + lookupKeyAsByte, err = hex.DecodeString(firstAddedGranteeLookupKey) + if err != nil { + t.Errorf("AddNewGrantee: expected no error, got %v", err) + } + result, _ = act.Lookup(lookupKeyAsByte) + hexEncodedEncryptedAK = hex.EncodeToString(result) + if len(hexEncodedEncryptedAK) != 64 { + t.Errorf("AddNewGrantee: expected encrypted access key length 64, got %d", len(hexEncodedEncryptedAK)) + } + + lookupKeyAsByte, err = hex.DecodeString(secondAddedGranteeLookupKey) + if err != nil { + t.Errorf("AddNewGrantee: expected no error, got %v", err) + } + result, _ = act.Lookup(lookupKeyAsByte) + hexEncodedEncryptedAK = hex.EncodeToString(result) + if len(hexEncodedEncryptedAK) != 64 { + t.Errorf("AddNewGrantee: expected encrypted access key length 64, got %d", len(hexEncodedEncryptedAK)) + } +} + +func TestEncryptRef(t *testing.T) { + ref := "39a5ea87b141fe44aa609c3327ecd896c0e2122897f5f4bbacf74db1033c5559" + savedEncryptedRef := "230cdcfb2e67adddb2822b38f70105213ab3e4f97d03560bfbfbb218f487c5303e9aa9a97e62aa1a8003f162679e7c65e1c8e3aacaec2043fd5d2a4a7d69285e" + + al := setupAccessLogic2() + id0 := generateFixPrivateKey(0) + act := dynamicaccess.NewInMemoryAct() + decodedLookupKey, err := hex.DecodeString("bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a") + if err != nil { + t.Errorf("EncryptRef: expected no error, got %v", err) + } + + act.Add(decodedLookupKey, []byte("42")) + + encryptedRefValue, err := al.EncryptRef(act, &id0.PublicKey, swarm.NewAddress([]byte(ref))) + if err != nil { + t.Errorf("EncryptRef: expected no error, got %v", err) + } + + if encryptedRefValue.String() != savedEncryptedRef { + t.Errorf("EncryptRef: expected encrypted ref, got empty address") + } +} \ No newline at end of file diff --git a/pkg/dynamicaccess/act.go b/pkg/dynamicaccess/act.go index 1153f2fc868..1f1dbb85897 100644 --- a/pkg/dynamicaccess/act.go +++ b/pkg/dynamicaccess/act.go @@ -78,7 +78,7 @@ func (act *inMemoryAct) Lookup(key []byte) ([]byte, error) { } return bytes, nil } - return make([]byte, 0), nil + return nil, fmt.Errorf("key not found") } func (act *inMemoryAct) Load(addr swarm.Address) error { diff --git a/pkg/dynamicaccess/controller.go b/pkg/dynamicaccess/controller.go index 6267a878279..92363014c9b 100644 --- a/pkg/dynamicaccess/controller.go +++ b/pkg/dynamicaccess/controller.go @@ -14,7 +14,7 @@ type Controller interface { type defaultController struct { history History granteeManager GranteeManager - accessLogic AccessLogic + accessLogic ActLogic } func (c *defaultController) DownloadHandler(timestamp int64, enryptedRef swarm.Address, publisher *ecdsa.PublicKey, tag string) (swarm.Address, error) { @@ -22,7 +22,7 @@ func (c *defaultController) DownloadHandler(timestamp int64, enryptedRef swarm.A if err != nil { return swarm.EmptyAddress, err } - addr, err := c.accessLogic.Get(act, enryptedRef, *publisher, tag) + addr, err := c.accessLogic.Get(act, enryptedRef, publisher) return addr, err } @@ -31,13 +31,13 @@ func (c *defaultController) UploadHandler(ref swarm.Address, publisher *ecdsa.Pu if act == nil { // new feed act = NewInMemoryAct() - act = c.granteeManager.Publish(act, *publisher, topic) + act = c.granteeManager.Publish(act, publisher, topic) } //FIXME: check if ACT is consistent with the grantee list - return c.accessLogic.EncryptRef(act, *publisher, ref) + return c.accessLogic.EncryptRef(act, publisher, ref) } -func NewController(history History, granteeManager GranteeManager, accessLogic AccessLogic) Controller { +func NewController(history History, granteeManager GranteeManager, accessLogic ActLogic) Controller { return &defaultController{ history: history, granteeManager: granteeManager, diff --git a/pkg/dynamicaccess/grantee.go b/pkg/dynamicaccess/grantee.go index 2a07d84b3ed..dfd7015e49f 100644 --- a/pkg/dynamicaccess/grantee.go +++ b/pkg/dynamicaccess/grantee.go @@ -5,37 +5,22 @@ import ( ) type Grantee interface { - //? ÁTBESZÉLNI - // Revoke(topic string) error - // Publish(topic string) error - - // RevokeList(topic string, removeList []string, addList []string) (string, error) - // RevokeGrantees(topic string, removeList []string) (string, error) AddGrantees(topic string, addList []*ecdsa.PublicKey) error RemoveGrantees(topic string, removeList []*ecdsa.PublicKey) error GetGrantees(topic string) []*ecdsa.PublicKey } type defaultGrantee struct { - grantees map[string][]*ecdsa.PublicKey // Modified field name to start with an uppercase letter + grantees map[string][]*ecdsa.PublicKey } func (g *defaultGrantee) GetGrantees(topic string) []*ecdsa.PublicKey { - return g.grantees[topic] + grantees := g.grantees[topic] + keys := make([]*ecdsa.PublicKey, len(grantees)) + copy(keys, grantees) + return keys } -// func (g *defaultGrantee) Revoke(topic string) error { -// return nil -// } - -// func (g *defaultGrantee) RevokeList(topic string, removeList []string, addList []string) (string, error) { -// return "", nil -// } - -// func (g *defaultGrantee) Publish(topic string) error { -// return nil -// } - func (g *defaultGrantee) AddGrantees(topic string, addList []*ecdsa.PublicKey) error { g.grantees[topic] = append(g.grantees[topic], addList...) return nil @@ -44,14 +29,17 @@ func (g *defaultGrantee) AddGrantees(topic string, addList []*ecdsa.PublicKey) e func (g *defaultGrantee) RemoveGrantees(topic string, removeList []*ecdsa.PublicKey) error { for _, remove := range removeList { for i, grantee := range g.grantees[topic] { - if grantee == remove { - g.grantees[topic] = append(g.grantees[topic][:i], g.grantees[topic][i+1:]...) + if *grantee == *remove { + g.grantees[topic][i] = g.grantees[topic][len(g.grantees[topic])-1] + g.grantees[topic] = g.grantees[topic][:len(g.grantees[topic])-1] } } } + + return nil } func NewGrantee() Grantee { return &defaultGrantee{grantees: make(map[string][]*ecdsa.PublicKey)} -} +} \ No newline at end of file diff --git a/pkg/dynamicaccess/grantee_manager.go b/pkg/dynamicaccess/grantee_manager.go index 9172a05db03..6b287af6941 100644 --- a/pkg/dynamicaccess/grantee_manager.go +++ b/pkg/dynamicaccess/grantee_manager.go @@ -5,7 +5,7 @@ import "crypto/ecdsa" type GranteeManager interface { Get(topic string) []*ecdsa.PublicKey Add(topic string, addList []*ecdsa.PublicKey) error - Publish(act Act, publisher ecdsa.PublicKey, topic string) Act + Publish(act Act, publisher *ecdsa.PublicKey, topic string) Act // HandleGrantees(topic string, addList, removeList []*ecdsa.PublicKey) *Act @@ -16,11 +16,11 @@ type GranteeManager interface { var _ GranteeManager = (*granteeManager)(nil) type granteeManager struct { - accessLogic AccessLogic + accessLogic ActLogic granteeList Grantee } -func NewGranteeManager(al AccessLogic) *granteeManager { +func NewGranteeManager(al ActLogic) *granteeManager { return &granteeManager{accessLogic: al, granteeList: NewGrantee()} } @@ -32,10 +32,10 @@ func (gm *granteeManager) Add(topic string, addList []*ecdsa.PublicKey) error { return gm.granteeList.AddGrantees(topic, addList) } -func (gm *granteeManager) Publish(act Act, publisher ecdsa.PublicKey, topic string) Act { - gm.accessLogic.AddPublisher(act, publisher, "") +func (gm *granteeManager) Publish(act Act, publisher *ecdsa.PublicKey, topic string) Act { + gm.accessLogic.AddPublisher(act, publisher) for _, grantee := range gm.granteeList.GetGrantees(topic) { - gm.accessLogic.Add_New_Grantee_To_Content(act, publisher, *grantee) + gm.accessLogic.AddNewGranteeToContent(act, publisher, grantee) } return act } diff --git a/pkg/dynamicaccess/grantee_manager_test.go b/pkg/dynamicaccess/grantee_manager_test.go index bc90ef504ae..fc5969f4492 100644 --- a/pkg/dynamicaccess/grantee_manager_test.go +++ b/pkg/dynamicaccess/grantee_manager_test.go @@ -10,13 +10,13 @@ import ( "github.com/ethersphere/bee/pkg/dynamicaccess" ) -func setupAccessLogic(privateKey *ecdsa.PrivateKey) dynamicaccess.AccessLogic { +func setupAccessLogic(privateKey *ecdsa.PrivateKey) dynamicaccess.ActLogic { // privateKey, err := crypto.GenerateSecp256k1Key() // if err != nil { // errors.New("error creating private key") // } si := dynamicaccess.NewDefaultSession(privateKey) - al := dynamicaccess.NewAccessLogic(si) + al := dynamicaccess.NewLogic(si) return al } @@ -36,6 +36,6 @@ func TestAdd(t *testing.T) { if err != nil { t.Errorf("Add() returned an error") } - m.Publish(act, pub.PublicKey, "topic") + m.Publish(act, &pub.PublicKey, "topic") fmt.Println("") } diff --git a/pkg/dynamicaccess/grantee_test.go b/pkg/dynamicaccess/grantee_test.go index 2d795ab2eef..7418a741d77 100644 --- a/pkg/dynamicaccess/grantee_test.go +++ b/pkg/dynamicaccess/grantee_test.go @@ -10,96 +10,93 @@ import ( "github.com/ethersphere/bee/pkg/dynamicaccess" ) -// func TestGranteeRevoke(t *testing.T) { -// err := NewGrantee().Revoke("") -// if err != nil { -// t.Errorf("Error revoking grantee: %v", err) -// } -// } - -/*func TestGranteeRevokeList(t *testing.T) { - _, err := NewGrantee().RevokeList("", nil, nil) - if err != nil { - t.Errorf("Error revoking list of grantees: %v", err) - } -}*/ - -// func TestGranteePublish(t *testing.T) { -// err := NewGrantee().Publish("") -// if err != nil { -// t.Errorf("Error publishing grantee: %v", err) -// } -// } - func TestGranteeAddGrantees(t *testing.T) { - // Create a new Grantee grantee := dynamicaccess.NewGrantee() - // Generate some dummy ecdsa.PublicKey values - key1, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - key2, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + key1, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Errorf("Expected no error, got %v", err) + } + + key2, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Errorf("Expected no error, got %v", err) + } - // Add the keys to the grantee addList := []*ecdsa.PublicKey{&key1.PublicKey, &key2.PublicKey} - err := grantee.AddGrantees("topicName", addList) - grantees := grantee.GetGrantees("topicName") - // Check for errors + exampleTopic := "topic" + err = grantee.AddGrantees(exampleTopic, addList) + if err != nil { - t.Fatalf("Expected no error, got %v", err) + t.Errorf("Expected no error, got %v", err) } - // Check if the keys were added correctly + grantees := grantee.GetGrantees(exampleTopic) if !reflect.DeepEqual(grantees, addList) { t.Errorf("Expected grantees %v, got %v", addList, grantees) } } func TestRemoveGrantees(t *testing.T) { - // Create a new Grantee grantee := dynamicaccess.NewGrantee() - // Generate some dummy ecdsa.PublicKey values - key1, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - key2, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + key1, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Errorf("Expected no error, got %v", err) + } + + key2, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Errorf("Expected no error, got %v", err) + } - // Add the keys to the grantee addList := []*ecdsa.PublicKey{&key1.PublicKey, &key2.PublicKey} - grantee.AddGrantees("topicName", addList) + exampleTopic := "topic" + err = grantee.AddGrantees(exampleTopic, addList) + if err != nil { + t.Errorf("Expected no error, got %v", err) + } - // Remove one of the keys removeList := []*ecdsa.PublicKey{&key1.PublicKey} - err := grantee.RemoveGrantees("topicName", removeList) - grantees := grantee.GetGrantees("topicName") - - // Check for errors + err = grantee.RemoveGrantees(exampleTopic, removeList) if err != nil { - t.Fatalf("Expected no error, got %v", err) + t.Errorf("Expected no error, got %v", err) } - // Check if the key was removed correctly + grantees := grantee.GetGrantees(exampleTopic) expectedGrantees := []*ecdsa.PublicKey{&key2.PublicKey} - if !reflect.DeepEqual(grantees, expectedGrantees) { - t.Errorf("Expected grantees %v, got %v", expectedGrantees, grantees) + + for i, grantee := range grantees { + if grantee != expectedGrantees[i] { + t.Errorf("Expected grantee %v, got %v", expectedGrantees[i], grantee) + } } } func TestGetGrantees(t *testing.T) { - // Create a new Grantee grantee := dynamicaccess.NewGrantee() - // Generate some dummy ecdsa.PublicKey values - key1, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - key2, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + key1, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Errorf("Expected no error, got %v", err) + } - // Add the keys to the grantee - addList := []*ecdsa.PublicKey{&key1.PublicKey, &key2.PublicKey} - grantee.AddGrantees("topicName", addList) + key2, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Errorf("Expected no error, got %v", err) + } - // Get the grantees - grantees := grantee.GetGrantees("topicName") + addList := []*ecdsa.PublicKey{&key1.PublicKey, &key2.PublicKey} + exampleTopic := "topic" + err = grantee.AddGrantees(exampleTopic, addList) + if err != nil { + t.Errorf("Expected no error, got %v", err) + } - // Check if the grantees were returned correctly - if !reflect.DeepEqual(grantees, addList) { - t.Errorf("Expected grantees %v, got %v", addList, grantees) + grantees := grantee.GetGrantees(exampleTopic) + for i, grantee := range grantees { + if grantee != addList[i] { + t.Errorf("Expected grantee %v, got %v", addList[i], grantee) + } } -} +} \ No newline at end of file From a56c5261f780cb95db391dcc48fbc878dff44bc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferenc=20S=C3=A1rai?= Date: Fri, 22 Mar 2024 13:09:37 +0100 Subject: [PATCH 13/34] Act kvs merge (#22) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * grantee container and access logc tests are passed * refactored access logic and grantee container * PR 19 comments resolving * Refactor * Refactor * working manifest ACT with basic tests * (refactor:) Refactor act_test * (refactor:) Refactor kvs -> kvs.manifest, kvs.memory * (refactror:) kvs * refactor kvs contsructors --------- Co-authored-by: Roland Seres Co-authored-by: Bálint Ujvári Co-authored-by: Ferenc Sárai --- pkg/dynamicaccess/accesslogic.go | 50 +++++----- pkg/dynamicaccess/accesslogic_test.go | 72 +++++++------- pkg/dynamicaccess/act.go | 110 +++++++--------------- pkg/dynamicaccess/act_test.go | 72 ++++++++++++-- pkg/dynamicaccess/container.go | 7 -- pkg/dynamicaccess/controller.go | 18 ++-- pkg/dynamicaccess/controller_test.go | 2 +- pkg/dynamicaccess/grantee_manager.go | 16 ++-- pkg/dynamicaccess/grantee_manager_test.go | 11 +-- pkg/dynamicaccess/history_test.go | 9 +- pkg/dynamicaccess/mock/act.go | 18 ++-- pkg/kvs/kvs.go | 34 +++++++ pkg/kvs/manifest/kvs.go | 86 +++++++++++++++++ pkg/kvs/memory/kvs.go | 67 +++++++++++++ 14 files changed, 388 insertions(+), 184 deletions(-) delete mode 100644 pkg/dynamicaccess/container.go create mode 100644 pkg/kvs/kvs.go create mode 100644 pkg/kvs/manifest/kvs.go create mode 100644 pkg/kvs/memory/kvs.go diff --git a/pkg/dynamicaccess/accesslogic.go b/pkg/dynamicaccess/accesslogic.go index 417a4a40878..4b5abcd739e 100644 --- a/pkg/dynamicaccess/accesslogic.go +++ b/pkg/dynamicaccess/accesslogic.go @@ -14,24 +14,25 @@ var hashFunc = sha3.NewLegacyKeccak256 // Logic has the responsibility to return a ref for a given grantee and create new encrypted reference for a grantee type Logic interface { // Adds a new grantee to the ACT - AddNewGranteeToContent(act Act, publisherPubKey, granteePubKey *ecdsa.PublicKey) (Act, error) - // Get will return a decrypted reference, for given encrypted reference and grantee !!!!!!!!!!!!!!!!!!!!! - Get(act Act, encryped_ref swarm.Address, publisher *ecdsa.PublicKey) (swarm.Address, error) + AddNewGranteeToContent(rootHash swarm.Address, publisherPubKey, granteePubKey *ecdsa.PublicKey) (swarm.Address, error) + // Get will return a decrypted reference, for given encrypted reference and grantee + Get(rootHash swarm.Address, encryped_ref swarm.Address, publisher *ecdsa.PublicKey) (swarm.Address, error) } type ActLogic struct { session Session + act Act } var _ Logic = (*ActLogic)(nil) // Adds a new publisher to an empty act -func (al ActLogic) AddPublisher(act Act, publisher *ecdsa.PublicKey) (Act, error) { +func (al ActLogic) AddPublisher(rootHash swarm.Address, publisher *ecdsa.PublicKey) (swarm.Address, error) { accessKey := encryption.GenerateRandomKey(encryption.KeyLength) keys, err := al.getKeys(publisher) if err != nil { - return nil, err + return swarm.EmptyAddress, err } lookupKey := keys[0] accessKeyEncryptionKey := keys[1] @@ -39,17 +40,15 @@ func (al ActLogic) AddPublisher(act Act, publisher *ecdsa.PublicKey) (Act, error accessKeyCipher := encryption.New(encryption.Key(accessKeyEncryptionKey), 0, uint32(0), hashFunc) encryptedAccessKey, err := accessKeyCipher.Encrypt([]byte(accessKey)) if err != nil { - return nil, err + return swarm.EmptyAddress, err } - act.Add(lookupKey, encryptedAccessKey) - - return act, nil + return al.act.Add(rootHash, lookupKey, encryptedAccessKey) } // Encrypts a SWARM reference for a publisher -func (al ActLogic) EncryptRef(act Act, publisherPubKey *ecdsa.PublicKey, ref swarm.Address) (swarm.Address, error) { - accessKey := al.getAccessKey(act, publisherPubKey) +func (al ActLogic) EncryptRef(rootHash swarm.Address, publisherPubKey *ecdsa.PublicKey, ref swarm.Address) (swarm.Address, error) { + accessKey := al.getAccessKey(rootHash, publisherPubKey) refCipher := encryption.New(accessKey, 0, uint32(0), hashFunc) encryptedRef, _ := refCipher.Encrypt(ref.Bytes()) @@ -57,14 +56,14 @@ func (al ActLogic) EncryptRef(act Act, publisherPubKey *ecdsa.PublicKey, ref swa } // Adds a new grantee to the ACT -func (al ActLogic) AddNewGranteeToContent(act Act, publisherPubKey, granteePubKey *ecdsa.PublicKey) (Act, error) { +func (al ActLogic) AddNewGranteeToContent(rootHash swarm.Address, publisherPubKey, granteePubKey *ecdsa.PublicKey) (swarm.Address, error) { // Get previously generated access key - accessKey := al.getAccessKey(act, publisherPubKey) + accessKey := al.getAccessKey(rootHash, publisherPubKey) // Encrypt the access key for the new Grantee keys, err := al.getKeys(granteePubKey) if err != nil { - return nil, err + return swarm.EmptyAddress, err } lookupKey := keys[0] accessKeyEncryptionKey := keys[1] @@ -73,18 +72,16 @@ func (al ActLogic) AddNewGranteeToContent(act Act, publisherPubKey, granteePubKe cipher := encryption.New(encryption.Key(accessKeyEncryptionKey), 0, uint32(0), hashFunc) granteeEncryptedAccessKey, err := cipher.Encrypt(accessKey) if err != nil { - return nil, err + return swarm.EmptyAddress, err } // Add the new encrypted access key for the Act - act.Add(lookupKey, granteeEncryptedAccessKey) - - return act, nil + return al.act.Add(rootHash, lookupKey, granteeEncryptedAccessKey) } // Will return the access key for a publisher (public key) -func (al *ActLogic) getAccessKey(act Act, publisherPubKey *ecdsa.PublicKey) []byte { +func (al *ActLogic) getAccessKey(rootHash swarm.Address, publisherPubKey *ecdsa.PublicKey) []byte { keys, err := al.getKeys(publisherPubKey) if err != nil { return nil @@ -93,7 +90,7 @@ func (al *ActLogic) getAccessKey(act Act, publisherPubKey *ecdsa.PublicKey) []by publisherAKDecryptionKey := keys[1] accessKeyDecryptionCipher := encryption.New(encryption.Key(publisherAKDecryptionKey), 0, uint32(0), hashFunc) - encryptedAK, err := al.getEncryptedAccessKey(act, publisherLookupKey) + encryptedAK, err := al.getEncryptedAccessKey(rootHash, publisherLookupKey) if err != nil { return nil } @@ -119,8 +116,8 @@ func (al *ActLogic) getKeys(publicKey *ecdsa.PublicKey) ([][]byte, error) { } // Gets the encrypted access key for a given grantee -func (al *ActLogic) getEncryptedAccessKey(act Act, lookup_key []byte) ([]byte, error) { - val, err := act.Lookup(lookup_key) +func (al *ActLogic) getEncryptedAccessKey(rootHash swarm.Address, lookup_key []byte) ([]byte, error) { + val, err := al.act.Lookup(rootHash, lookup_key) if err != nil { return nil, err } @@ -128,7 +125,7 @@ func (al *ActLogic) getEncryptedAccessKey(act Act, lookup_key []byte) ([]byte, e } // Get will return a decrypted reference, for given encrypted reference and grantee -func (al ActLogic) Get(act Act, encryped_ref swarm.Address, grantee *ecdsa.PublicKey) (swarm.Address, error) { +func (al ActLogic) Get(rootHash swarm.Address, encryped_ref swarm.Address, grantee *ecdsa.PublicKey) (swarm.Address, error) { if encryped_ref.Compare(swarm.EmptyAddress) == 0 { return swarm.EmptyAddress, fmt.Errorf("encrypted ref not provided") } @@ -144,7 +141,7 @@ func (al ActLogic) Get(act Act, encryped_ref swarm.Address, grantee *ecdsa.Publi accessKeyDecryptionKey := keys[1] // Lookup encrypted access key from the ACT manifest - encryptedAccessKey, err := al.getEncryptedAccessKey(act, lookupKey) + encryptedAccessKey, err := al.getEncryptedAccessKey(rootHash, lookupKey) if err != nil { return swarm.EmptyAddress, err } @@ -166,8 +163,9 @@ func (al ActLogic) Get(act Act, encryped_ref swarm.Address, grantee *ecdsa.Publi return swarm.NewAddress(ref), nil } -func NewLogic(s Session) ActLogic { +func NewLogic(s Session, act Act) ActLogic { return ActLogic{ session: s, + act: act, } -} \ No newline at end of file +} diff --git a/pkg/dynamicaccess/accesslogic_test.go b/pkg/dynamicaccess/accesslogic_test.go index 46a4cc39ec6..b87bac84ffd 100644 --- a/pkg/dynamicaccess/accesslogic_test.go +++ b/pkg/dynamicaccess/accesslogic_test.go @@ -8,14 +8,15 @@ import ( "testing" "github.com/ethersphere/bee/pkg/dynamicaccess" + mockstorer "github.com/ethersphere/bee/pkg/storer/mock" "github.com/ethersphere/bee/pkg/swarm" ) // Generates a new test environment with a fix private key -func setupAccessLogic2() dynamicaccess.ActLogic { +func setupAccessLogic2(act dynamicaccess.Act) dynamicaccess.ActLogic { privateKey := generateFixPrivateKey(1000) diffieHellman := dynamicaccess.NewDefaultSession(&privateKey) - al := dynamicaccess.NewLogic(diffieHellman) + al := dynamicaccess.NewLogic(diffieHellman, act) return al } @@ -39,11 +40,11 @@ func generateFixPrivateKey(input int64) ecdsa.PrivateKey { } func TestGet_Success(t *testing.T) { - al := setupAccessLogic2() id0 := generateFixPrivateKey(0) - - act := dynamicaccess.NewInMemoryAct() - act, err := al.AddPublisher(act, &id0.PublicKey) + var mockStorer = mockstorer.New() + act := dynamicaccess.NewInManifestAct(mockStorer) + al := setupAccessLogic2(act) + ref, err := al.AddPublisher(swarm.EmptyAddress, &id0.PublicKey) if err != nil { t.Errorf("AddPublisher: expected no error, got %v", err) } @@ -53,20 +54,20 @@ func TestGet_Success(t *testing.T) { expectedRef := swarm.NewAddress(byteRef) t.Logf("encryptedRef: %s", expectedRef.String()) - encryptedRef, err := al.EncryptRef(act, &id0.PublicKey, expectedRef) + encryptedRef, err := al.EncryptRef(ref, &id0.PublicKey, expectedRef) t.Logf("encryptedRef: %s", encryptedRef.String()) if err != nil { t.Errorf("There was an error while calling EncryptRef: ") t.Error(err) } - ref, err := al.Get(act, encryptedRef, &id0.PublicKey) + acutalRef, err := al.Get(ref, encryptedRef, &id0.PublicKey) if err != nil { t.Errorf("There was an error while calling Get: ") t.Error(err) } - if expectedRef.Compare(ref) != 0 { + if expectedRef.Compare(acutalRef) != 0 { t.Errorf("Get gave back wrong Swarm reference!") } @@ -74,51 +75,55 @@ func TestGet_Success(t *testing.T) { // This test function tests those cases where different parameters are missing func TestGet_Error(t *testing.T) { - al := setupAccessLogic2() id0 := generateFixPrivateKey(0) act := dynamicaccess.NewInMemoryAct() - act, err := al.AddPublisher(act, &id0.PublicKey) + al := setupAccessLogic2(act) + ref, err := al.AddPublisher(swarm.EmptyAddress, &id0.PublicKey) if err != nil { t.Errorf("AddPublisher: expected no error, got %v", err) } expectedRef := "39a5ea87b141fe44aa609c3327ecd896c0e2122897f5f4bbacf74db1033c5559" - encryptedRef, _ := al.EncryptRef(act, &id0.PublicKey, swarm.NewAddress([]byte(expectedRef))) + encryptedRef, _ := al.EncryptRef(ref, &id0.PublicKey, swarm.NewAddress([]byte(expectedRef))) - _, err = al.Get(dynamicaccess.NewInMemoryAct(), encryptedRef, &id0.PublicKey) + r, err := al.Get(swarm.RandAddress(t), encryptedRef, &id0.PublicKey) if err == nil { + t.Logf("r: %s", r.String()) t.Errorf("Get should give back encrypted access key not found error!") } - refTwo, _ := al.Get(act, swarm.EmptyAddress, &id0.PublicKey) + refTwo, _ := al.Get(swarm.RandAddress(t), swarm.EmptyAddress, &id0.PublicKey) if swarm.EmptyAddress.Compare(refTwo) != 0 { t.Errorf("Get should give back empty string if encrypted ref not provided!") } - _, err = al.Get(act, encryptedRef, nil) + _, err = al.Get(swarm.RandAddress(t), encryptedRef, nil) if err == nil { t.Errorf("Get should give back error if grantee not provided!") } } func TestAddPublisher(t *testing.T) { - al := setupAccessLogic2() id0 := generateFixPrivateKey(0) savedLookupKey := "bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a" act := dynamicaccess.NewInMemoryAct() - act, err := al.AddPublisher(act, &id0.PublicKey) + al := setupAccessLogic2(act) + ref, err := al.AddPublisher(swarm.EmptyAddress, &id0.PublicKey) if err != nil { t.Errorf("AddPublisher: expected no error, got %v", err) } decodedSavedLookupKey, err := hex.DecodeString(savedLookupKey) if err != nil { - t.Errorf("AddPublisher: expected no error, got %v", err) + t.Errorf("DecodeString: expected no error, got %v", err) } - encryptedAccessKey, _ := act.Lookup(decodedSavedLookupKey) + encryptedAccessKey, err := act.Lookup(ref, decodedSavedLookupKey) + if err != nil { + t.Errorf("Lookup: expected no error, got %v", err) + } decodedEncryptedAccessKey := hex.EncodeToString(encryptedAccessKey) // A random value is returned so it is only possibly to check the length of the returned value @@ -132,7 +137,6 @@ func TestAddPublisher(t *testing.T) { } func TestAdd_New_Grantee_To_Content(t *testing.T) { - al := setupAccessLogic2() id0 := generateFixPrivateKey(0) id1 := generateFixPrivateKey(1) @@ -143,17 +147,18 @@ func TestAdd_New_Grantee_To_Content(t *testing.T) { secondAddedGranteeLookupKey := "8fe8dff7cd15a6a0095c1b25071a5691e7c901fd0b95857a96c0e4659b48716a" act := dynamicaccess.NewInMemoryAct() - act, err := al.AddPublisher(act, &id0.PublicKey) - if err != nil { + al := setupAccessLogic2(act) + ref, err := al.AddPublisher(swarm.EmptyAddress, &id0.PublicKey) + if err != nil { t.Errorf("AddNewGrantee: expected no error, got %v", err) } - act, err = al.AddNewGranteeToContent(act, &id0.PublicKey, &id1.PublicKey) + ref, err = al.AddNewGranteeToContent(ref, &id0.PublicKey, &id1.PublicKey) if err != nil { t.Errorf("AddNewGrantee: expected no error, got %v", err) } - act, err = al.AddNewGranteeToContent(act, &id0.PublicKey, &id2.PublicKey) + ref, err = al.AddNewGranteeToContent(ref, &id0.PublicKey, &id2.PublicKey) if err != nil { t.Errorf("AddNewGrantee: expected no error, got %v", err) } @@ -162,7 +167,7 @@ func TestAdd_New_Grantee_To_Content(t *testing.T) { if err != nil { t.Errorf("AddNewGrantee: expected no error, got %v", err) } - result, _ := act.Lookup(lookupKeyAsByte) + result, _ := act.Lookup(ref, lookupKeyAsByte) hexEncodedEncryptedAK := hex.EncodeToString(result) if len(hexEncodedEncryptedAK) != 64 { t.Errorf("AddNewGrantee: expected encrypted access key length 64, got %d", len(hexEncodedEncryptedAK)) @@ -172,7 +177,7 @@ func TestAdd_New_Grantee_To_Content(t *testing.T) { if err != nil { t.Errorf("AddNewGrantee: expected no error, got %v", err) } - result, _ = act.Lookup(lookupKeyAsByte) + result, _ = act.Lookup(ref, lookupKeyAsByte) hexEncodedEncryptedAK = hex.EncodeToString(result) if len(hexEncodedEncryptedAK) != 64 { t.Errorf("AddNewGrantee: expected encrypted access key length 64, got %d", len(hexEncodedEncryptedAK)) @@ -182,7 +187,7 @@ func TestAdd_New_Grantee_To_Content(t *testing.T) { if err != nil { t.Errorf("AddNewGrantee: expected no error, got %v", err) } - result, _ = act.Lookup(lookupKeyAsByte) + result, _ = act.Lookup(ref, lookupKeyAsByte) hexEncodedEncryptedAK = hex.EncodeToString(result) if len(hexEncodedEncryptedAK) != 64 { t.Errorf("AddNewGrantee: expected encrypted access key length 64, got %d", len(hexEncodedEncryptedAK)) @@ -193,17 +198,20 @@ func TestEncryptRef(t *testing.T) { ref := "39a5ea87b141fe44aa609c3327ecd896c0e2122897f5f4bbacf74db1033c5559" savedEncryptedRef := "230cdcfb2e67adddb2822b38f70105213ab3e4f97d03560bfbfbb218f487c5303e9aa9a97e62aa1a8003f162679e7c65e1c8e3aacaec2043fd5d2a4a7d69285e" - al := setupAccessLogic2() id0 := generateFixPrivateKey(0) act := dynamicaccess.NewInMemoryAct() + al := setupAccessLogic2(act) decodedLookupKey, err := hex.DecodeString("bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a") if err != nil { t.Errorf("EncryptRef: expected no error, got %v", err) } - act.Add(decodedLookupKey, []byte("42")) - - encryptedRefValue, err := al.EncryptRef(act, &id0.PublicKey, swarm.NewAddress([]byte(ref))) + addRef, err := act.Add(swarm.EmptyAddress, decodedLookupKey, []byte("42")) + if err != nil { + t.Errorf("Add: expected no error, got %v", err) + } + + encryptedRefValue, err := al.EncryptRef(addRef, &id0.PublicKey, swarm.NewAddress([]byte(ref))) if err != nil { t.Errorf("EncryptRef: expected no error, got %v", err) } @@ -211,4 +219,4 @@ func TestEncryptRef(t *testing.T) { if encryptedRefValue.String() != savedEncryptedRef { t.Errorf("EncryptRef: expected encrypted ref, got empty address") } -} \ No newline at end of file +} diff --git a/pkg/dynamicaccess/act.go b/pkg/dynamicaccess/act.go index 1f1dbb85897..f7595b5b1ec 100644 --- a/pkg/dynamicaccess/act.go +++ b/pkg/dynamicaccess/act.go @@ -5,107 +5,61 @@ package dynamicaccess import ( - "crypto/rand" - "encoding/hex" - "fmt" - "sync" - - "github.com/ethersphere/bee/pkg/manifest" + "github.com/ethersphere/bee/pkg/api" + "github.com/ethersphere/bee/pkg/kvs" + kvsmanifest "github.com/ethersphere/bee/pkg/kvs/manifest" + kvsmemory "github.com/ethersphere/bee/pkg/kvs/memory" "github.com/ethersphere/bee/pkg/swarm" ) -var lock = &sync.Mutex{} - -type single struct { - memoryMock map[string]manifest.Entry -} - -var singleInMemorySwarm *single - -func getInMemorySwarm() *single { - if singleInMemorySwarm == nil { - lock.Lock() - defer lock.Unlock() - if singleInMemorySwarm == nil { - singleInMemorySwarm = &single{ - memoryMock: make(map[string]manifest.Entry)} - } - } - return singleInMemorySwarm -} - -func getMemory() map[string]manifest.Entry { - ch := make(chan *single) - go func() { - ch <- getInMemorySwarm() - }() - mem := <-ch - return mem.memoryMock -} - // Act represents an interface for accessing and manipulating data. type Act interface { // Add adds a key-value pair to the data store. - Add(key []byte, val []byte) error + Add(rootHash swarm.Address, key []byte, val []byte) (swarm.Address, error) // Lookup retrieves the value associated with the given key from the data store. - Lookup(key []byte) ([]byte, error) + Lookup(rootHash swarm.Address, key []byte) ([]byte, error) // Load loads the data store from the given address. - Load(addr swarm.Address) error + //Load(addr swarm.Address) error // Store stores the current state of the data store and returns the address of the ACT. - Store() (swarm.Address, error) + //Store() (swarm.Address, error) } -var _ Act = (*inMemoryAct)(nil) - -// inMemoryAct is a simple implementation of the Act interface, with in memory storage. -type inMemoryAct struct { - container map[string]string +// inKvsAct is an implementation of the Act interface that uses kvs storage. +type inKvsAct struct { + storage kvs.KeyValueStore } -func (act *inMemoryAct) Add(key []byte, val []byte) error { - act.container[hex.EncodeToString(key)] = hex.EncodeToString(val) - return nil +// Add adds a key-value pair to the in-memory data store. +func (act *inKvsAct) Add(rootHash swarm.Address, key []byte, val []byte) (swarm.Address, error) { + return act.storage.Put(rootHash, key, val) } -func (act *inMemoryAct) Lookup(key []byte) ([]byte, error) { - if key, ok := act.container[hex.EncodeToString(key)]; ok { - bytes, err := hex.DecodeString(key) - if err != nil { - return nil, err - } - return bytes, nil - } - return nil, fmt.Errorf("key not found") +// Lookup retrieves the value associated with the given key from the in-memory data store. +func (act *inKvsAct) Lookup(rootHash swarm.Address, key []byte) ([]byte, error) { + return act.storage.Get(rootHash, key) } -func (act *inMemoryAct) Load(addr swarm.Address) error { - memory := getMemory() - me := memory[addr.String()] - if me == nil { - return fmt.Errorf("ACT not found at address: %s", addr.String()) +// NewInMemoryAct creates a new instance of the Act interface with in-memory storage. +func NewInMemoryAct() Act { + s, err := kvs.NewKeyValueStore(nil, kvsmemory.KvsTypeMemory) + if err != nil { + return nil } - act.container = me.Metadata() - return nil -} - -func (act *inMemoryAct) Store() (swarm.Address, error) { - // Generate a random swarm.Address - b := make([]byte, 32) - if _, err := rand.Read(b); err != nil { - return swarm.EmptyAddress, fmt.Errorf("failed to generate random address: %w", err) + return &inKvsAct{ + storage: s, } - swarm_ref := swarm.NewAddress(b) - mem := getMemory() - mem[swarm_ref.String()] = manifest.NewEntry(swarm_ref, act.container) - - return swarm_ref, nil } -func NewInMemoryAct() Act { - return &inMemoryAct{ - container: make(map[string]string), +// NewInManifestAct creates a new instance of the Act interface with manifest storage. +func NewInManifestAct(storer api.Storer) Act { + s, err := kvs.NewKeyValueStore(storer, kvsmanifest.KvsTypeManifest) + if err != nil { + return nil + } + return &inKvsAct{ + storage: s, } } diff --git a/pkg/dynamicaccess/act_test.go b/pkg/dynamicaccess/act_test.go index 3480077102a..6522a76b9ed 100644 --- a/pkg/dynamicaccess/act_test.go +++ b/pkg/dynamicaccess/act_test.go @@ -10,24 +10,77 @@ import ( "testing" "github.com/ethersphere/bee/pkg/dynamicaccess" + mockstorer "github.com/ethersphere/bee/pkg/storer/mock" "github.com/ethersphere/bee/pkg/swarm" ) +type ActType int + +const ( + Memory ActType = iota + Manifest +) + +func (a ActType) String() string { + return [...]string{"Memory", "Manifest"}[a] +} + +var mockStorer = mockstorer.New() + +var acts = map[ActType]func() dynamicaccess.Act{ + Memory: dynamicaccess.NewInMemoryAct, + Manifest: func() dynamicaccess.Act { return dynamicaccess.NewInManifestAct(mockStorer) }, +} + func TestActAddLookup(t *testing.T) { - act := dynamicaccess.NewInMemoryAct() - lookupKey := swarm.RandAddress(t).Bytes() - encryptedAccesskey := swarm.RandAddress(t).Bytes() - err := act.Add(lookupKey, encryptedAccesskey) - if err != nil { - t.Error("Add() should not return an error") + for actType, actCreate := range acts { + t.Logf("Running test for ActType: %s", actType) + act := actCreate() + + lookupKey := swarm.RandAddress(t).Bytes() + encryptedAccesskey := swarm.RandAddress(t).Bytes() + + ref, err := act.Add(swarm.EmptyAddress, lookupKey, encryptedAccesskey) + if err != nil { + t.Errorf("Add() should not return an error: %v", err) + } + + key, err := act.Lookup(ref, lookupKey) + if err != nil { + t.Errorf("Lookup() should not return an error: %v", err) + } + + if !bytes.Equal(key, encryptedAccesskey) { + t.Errorf("Get() value is not the expected %s != %s", hex.EncodeToString(key), hex.EncodeToString(encryptedAccesskey)) + } } +} + +func TestActAddLookupWithNew(t *testing.T) { + for actType, actCreate := range acts { + t.Logf("Running test for ActType: %s", actType) + act1 := actCreate() + lookupKey := swarm.RandAddress(t).Bytes() + encryptedAccesskey := swarm.RandAddress(t).Bytes() + + ref, err := act1.Add(swarm.EmptyAddress, lookupKey, encryptedAccesskey) + if err != nil { + t.Fatalf("Add() should not return an error: %v", err) + } + + act2 := actCreate() + key, err := act2.Lookup(ref, lookupKey) + if err != nil { + t.Fatalf("Lookup() should not return an error: %v", err) + } - key, _ := act.Lookup(lookupKey) - if !bytes.Equal(key, encryptedAccesskey) { - t.Errorf("Get() value is not the expected %s != %s", key, encryptedAccesskey) + if !bytes.Equal(key, encryptedAccesskey) { + t.Errorf("Get() value is not the expected %s != %s", hex.EncodeToString(key), hex.EncodeToString(encryptedAccesskey)) + } } } +/* func TestActStoreLoad(t *testing.T) { act := dynamicaccess.NewInMemoryAct() @@ -50,3 +103,4 @@ func TestActStoreLoad(t *testing.T) { t.Errorf("actualAct.Load() value is not the expected %s != %s", hex.EncodeToString(actualEak), hex.EncodeToString(encryptedAccesskey)) } } +*/ diff --git a/pkg/dynamicaccess/container.go b/pkg/dynamicaccess/container.go deleted file mode 100644 index ae683aa0fcd..00000000000 --- a/pkg/dynamicaccess/container.go +++ /dev/null @@ -1,7 +0,0 @@ -package dynamicaccess - -// iterator -type Container interface { - Add(oldItemKey string, oldRootHash string) (newRootHash string, err error) - Get(rootKey string) (value string, err error) -} diff --git a/pkg/dynamicaccess/controller.go b/pkg/dynamicaccess/controller.go index 92363014c9b..9ced8a4d873 100644 --- a/pkg/dynamicaccess/controller.go +++ b/pkg/dynamicaccess/controller.go @@ -18,23 +18,29 @@ type defaultController struct { } func (c *defaultController) DownloadHandler(timestamp int64, enryptedRef swarm.Address, publisher *ecdsa.PublicKey, tag string) (swarm.Address, error) { - act, err := c.history.Lookup(timestamp) + _, err := c.history.Lookup(timestamp) if err != nil { return swarm.EmptyAddress, err } - addr, err := c.accessLogic.Get(act, enryptedRef, publisher) + addr, err := c.accessLogic.Get(swarm.EmptyAddress, enryptedRef, publisher) return addr, err } func (c *defaultController) UploadHandler(ref swarm.Address, publisher *ecdsa.PublicKey, topic string) (swarm.Address, error) { - act, _ := c.history.Lookup(0) + act, err := c.history.Lookup(0) + if err != nil { + return swarm.EmptyAddress, err + } + var actRef swarm.Address if act == nil { // new feed - act = NewInMemoryAct() - act = c.granteeManager.Publish(act, publisher, topic) + actRef, err = c.granteeManager.Publish(swarm.EmptyAddress, publisher, topic) + if err != nil { + return swarm.EmptyAddress, err + } } //FIXME: check if ACT is consistent with the grantee list - return c.accessLogic.EncryptRef(act, publisher, ref) + return c.accessLogic.EncryptRef(actRef, publisher, ref) } func NewController(history History, granteeManager GranteeManager, accessLogic ActLogic) Controller { diff --git a/pkg/dynamicaccess/controller_test.go b/pkg/dynamicaccess/controller_test.go index 531470eceee..8f876232b05 100644 --- a/pkg/dynamicaccess/controller_test.go +++ b/pkg/dynamicaccess/controller_test.go @@ -24,7 +24,7 @@ func mockTestHistory(key, val []byte) dynamicaccess.History { now = time.Now() act = dynamicaccess.NewInMemoryAct() ) - act.Add(key, val) + act.Add(swarm.EmptyAddress, key, val) h.Insert(now.AddDate(-3, 0, 0).Unix(), act) return h } diff --git a/pkg/dynamicaccess/grantee_manager.go b/pkg/dynamicaccess/grantee_manager.go index 6b287af6941..e6884f77bea 100644 --- a/pkg/dynamicaccess/grantee_manager.go +++ b/pkg/dynamicaccess/grantee_manager.go @@ -1,11 +1,15 @@ package dynamicaccess -import "crypto/ecdsa" +import ( + "crypto/ecdsa" + + "github.com/ethersphere/bee/pkg/swarm" +) type GranteeManager interface { Get(topic string) []*ecdsa.PublicKey Add(topic string, addList []*ecdsa.PublicKey) error - Publish(act Act, publisher *ecdsa.PublicKey, topic string) Act + Publish(rootHash swarm.Address, publisher *ecdsa.PublicKey, topic string) (swarm.Address, error) // HandleGrantees(topic string, addList, removeList []*ecdsa.PublicKey) *Act @@ -32,10 +36,10 @@ func (gm *granteeManager) Add(topic string, addList []*ecdsa.PublicKey) error { return gm.granteeList.AddGrantees(topic, addList) } -func (gm *granteeManager) Publish(act Act, publisher *ecdsa.PublicKey, topic string) Act { - gm.accessLogic.AddPublisher(act, publisher) +func (gm *granteeManager) Publish(rootHash swarm.Address, publisher *ecdsa.PublicKey, topic string) (swarm.Address, error) { + ref, err := gm.accessLogic.AddPublisher(rootHash, publisher) for _, grantee := range gm.granteeList.GetGrantees(topic) { - gm.accessLogic.AddNewGranteeToContent(act, publisher, grantee) + ref, err = gm.accessLogic.AddNewGranteeToContent(ref, publisher, grantee) } - return act + return ref, err } diff --git a/pkg/dynamicaccess/grantee_manager_test.go b/pkg/dynamicaccess/grantee_manager_test.go index fc5969f4492..2144fd1410a 100644 --- a/pkg/dynamicaccess/grantee_manager_test.go +++ b/pkg/dynamicaccess/grantee_manager_test.go @@ -8,21 +8,18 @@ import ( "testing" "github.com/ethersphere/bee/pkg/dynamicaccess" + "github.com/ethersphere/bee/pkg/swarm" ) func setupAccessLogic(privateKey *ecdsa.PrivateKey) dynamicaccess.ActLogic { - // privateKey, err := crypto.GenerateSecp256k1Key() - // if err != nil { - // errors.New("error creating private key") - // } + act := dynamicaccess.NewInMemoryAct() si := dynamicaccess.NewDefaultSession(privateKey) - al := dynamicaccess.NewLogic(si) + al := dynamicaccess.NewLogic(si, act) return al } func TestAdd(t *testing.T) { - act := dynamicaccess.NewInMemoryAct() m := dynamicaccess.NewGranteeManager(setupAccessLogic(getPrivateKey())) pub, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) @@ -36,6 +33,6 @@ func TestAdd(t *testing.T) { if err != nil { t.Errorf("Add() returned an error") } - m.Publish(act, &pub.PublicKey, "topic") + m.Publish(swarm.EmptyAddress, &pub.PublicKey, "topic") fmt.Println("") } diff --git a/pkg/dynamicaccess/history_test.go b/pkg/dynamicaccess/history_test.go index a58aa44ecaa..e501e075697 100644 --- a/pkg/dynamicaccess/history_test.go +++ b/pkg/dynamicaccess/history_test.go @@ -7,6 +7,7 @@ import ( "github.com/ethersphere/bee/pkg/dynamicaccess" "github.com/ethersphere/bee/pkg/dynamicaccess/mock" + "github.com/ethersphere/bee/pkg/swarm" "github.com/stretchr/testify/assert" ) @@ -32,7 +33,7 @@ func TestHistoryLookup(t *testing.T) { for _, tt := range tests { t.Run("", func(t *testing.T) { actAt, _ := h.Lookup(tt.input) - output, _ := actAt.Lookup([]byte("key1")) + output, _ := actAt.Lookup(swarm.EmptyAddress, []byte("key1")) assert.Equal(t, output, hex.EncodeToString([]byte(tt.expected))) }) } @@ -46,9 +47,9 @@ func prepareTestHistory() dynamicaccess.History { act2 = dynamicaccess.NewInMemoryAct() act3 = dynamicaccess.NewInMemoryAct() ) - act1.Add([]byte("key1"), []byte("value1")) - act2.Add([]byte("key1"), []byte("value2")) - act3.Add([]byte("key1"), []byte("value3")) + act1.Add(swarm.EmptyAddress, []byte("key1"), []byte("value1")) + act2.Add(swarm.EmptyAddress, []byte("key1"), []byte("value2")) + act3.Add(swarm.EmptyAddress, []byte("key1"), []byte("value3")) h.Insert(now.AddDate(-3, 0, 0).Unix(), act1) h.Insert(now.AddDate(-2, 0, 0).Unix(), act2) diff --git a/pkg/dynamicaccess/mock/act.go b/pkg/dynamicaccess/mock/act.go index a915f38f123..721a4778829 100644 --- a/pkg/dynamicaccess/mock/act.go +++ b/pkg/dynamicaccess/mock/act.go @@ -10,26 +10,26 @@ import ( ) type ActMock struct { - AddFunc func(key []byte, val []byte) error - LookupFunc func(key []byte) ([]byte, error) + AddFunc func(root swarm.Address, key []byte, val []byte) (swarm.Address, error) + LookupFunc func(root swarm.Address, key []byte) ([]byte, error) LoadFunc func(addr swarm.Address) error StoreFunc func() (swarm.Address, error) } var _ dynamicaccess.Act = (*ActMock)(nil) -func (act *ActMock) Add(key []byte, val []byte) error { +func (act *ActMock) Add(root swarm.Address, key []byte, val []byte) (swarm.Address, error) { if act.AddFunc == nil { - return nil + return swarm.EmptyAddress, nil } - return act.AddFunc(key, val) + return act.AddFunc(root, key, val) } -func (act *ActMock) Lookup(key []byte) ([]byte, error) { +func (act *ActMock) Lookup(root swarm.Address, key []byte) ([]byte, error) { if act.LookupFunc == nil { return make([]byte, 0), nil } - return act.LookupFunc(key) + return act.LookupFunc(root, key) } func (act *ActMock) Load(addr swarm.Address) error { @@ -46,7 +46,9 @@ func (act *ActMock) Store() (swarm.Address, error) { return act.StoreFunc() } -func NewActMock(addFunc func(key []byte, val []byte) error, getFunc func(key []byte) ([]byte, error)) dynamicaccess.Act { +func NewActMock( + addFunc func(swarm.Address, []byte, []byte) (swarm.Address, error), + getFunc func(swarm.Address, []byte) ([]byte, error)) dynamicaccess.Act { return &ActMock{ AddFunc: addFunc, LookupFunc: getFunc, diff --git a/pkg/kvs/kvs.go b/pkg/kvs/kvs.go new file mode 100644 index 00000000000..65c2a0be8b9 --- /dev/null +++ b/pkg/kvs/kvs.go @@ -0,0 +1,34 @@ +package kvs + +import ( + "errors" + + "github.com/ethersphere/bee/pkg/api" + "github.com/ethersphere/bee/pkg/kvs/manifest" + "github.com/ethersphere/bee/pkg/kvs/memory" + "github.com/ethersphere/bee/pkg/swarm" +) + +var ErrInvalidKvsType = errors.New("kvs: invalid type") + +type KeyValueStore interface { + Get(rootHash swarm.Address, key []byte) ([]byte, error) + Put(rootHash swarm.Address, key, value []byte) (swarm.Address, error) +} + +// func NewDefaultKeyValueStore(storer api.Storer) (KeyValueStore, error) { +// return NewKeyValueStore(storer, memory.KvsTypeMemory) +// } + +func NewKeyValueStore(storer api.Storer, kvsType string) (KeyValueStore, error) { + switch kvsType { + case "": + return memory.NewMemoryKeyValueStore() + case memory.KvsTypeMemory: + return memory.NewMemoryKeyValueStore() + case manifest.KvsTypeManifest: + return manifest.NewManifestKeyValueStore(storer) + default: + return nil, ErrInvalidKvsType + } +} diff --git a/pkg/kvs/manifest/kvs.go b/pkg/kvs/manifest/kvs.go new file mode 100644 index 00000000000..19ca983d54a --- /dev/null +++ b/pkg/kvs/manifest/kvs.go @@ -0,0 +1,86 @@ +package manifest + +import ( + "context" + "encoding/hex" + + "github.com/ethersphere/bee/pkg/api" + "github.com/ethersphere/bee/pkg/file/loadsave" + "github.com/ethersphere/bee/pkg/file/pipeline" + "github.com/ethersphere/bee/pkg/file/pipeline/builder" + "github.com/ethersphere/bee/pkg/file/redundancy" + "github.com/ethersphere/bee/pkg/manifest" + "github.com/ethersphere/bee/pkg/storage" + "github.com/ethersphere/bee/pkg/swarm" +) + +const ( + // KvsTypeManifest represents + KvsTypeManifest = "Manifest" +) + +type ManifestKeyValueStore interface { + Get(rootHash swarm.Address, key []byte) ([]byte, error) + Put(rootHash swarm.Address, key, value []byte) (swarm.Address, error) +} + +type manifestKeyValueStore struct { + storer api.Storer +} + +// TODO: pass context as dep. +func (m *manifestKeyValueStore) Get(rootHash swarm.Address, key []byte) ([]byte, error) { + ls := loadsave.NewReadonly(m.storer.ChunkStore()) + // existing manif + manif, err := manifest.NewSimpleManifestReference(rootHash, ls) + if err != nil { + return nil, err + } + entry, err := manif.Lookup(context.Background(), hex.EncodeToString(key)) + if err != nil { + return nil, err + } + ref := entry.Reference() + return ref.Bytes(), nil +} + +func (m *manifestKeyValueStore) Put(rootHash swarm.Address, key []byte, value []byte) (swarm.Address, error) { + factory := requestPipelineFactory(context.Background(), m.storer.Cache(), false, redundancy.NONE) + ls := loadsave.New(m.storer.ChunkStore(), m.storer.Cache(), factory) + // existing manif + manif, err := manifest.NewSimpleManifestReference(rootHash, ls) + if err != nil { + // new manif + manif, err = manifest.NewSimpleManifest(ls) + if err != nil { + return swarm.EmptyAddress, err + } + } + err = manif.Add(context.Background(), hex.EncodeToString(key), manifest.NewEntry(swarm.NewAddress(value), map[string]string{})) + if err != nil { + return swarm.EmptyAddress, err + } + manifRef, err := manif.Store(context.Background()) + if err != nil { + return swarm.EmptyAddress, err + } + + putter := m.storer.DirectUpload() + err = putter.Done(manifRef) + if err != nil { + return swarm.EmptyAddress, err + } + return manifRef, nil +} + +func NewManifestKeyValueStore(storer api.Storer) (ManifestKeyValueStore, error) { + return &manifestKeyValueStore{ + storer: storer, + }, nil +} + +func requestPipelineFactory(ctx context.Context, s storage.Putter, encrypt bool, rLevel redundancy.Level) func() pipeline.Interface { + return func() pipeline.Interface { + return builder.NewPipelineBuilder(ctx, s, encrypt, rLevel) + } +} diff --git a/pkg/kvs/memory/kvs.go b/pkg/kvs/memory/kvs.go new file mode 100644 index 00000000000..3a1abc69c0e --- /dev/null +++ b/pkg/kvs/memory/kvs.go @@ -0,0 +1,67 @@ +package memory + +import ( + "encoding/hex" + "sync" + + "github.com/ethersphere/bee/pkg/swarm" +) + +const ( + // KvsTypeMemory represents + KvsTypeMemory = "Memory" +) + +type MemoryKeyValueStore interface { + Get(rootHash swarm.Address, key []byte) ([]byte, error) + Put(rootHash swarm.Address, key, value []byte) (swarm.Address, error) +} + +var lock = &sync.Mutex{} + +type single struct { + // TODO string -> []byte ? + memoryMock map[string][]byte +} + +var singleInMemorySwarm *single + +func getInMemorySwarm() *single { + if singleInMemorySwarm == nil { + lock.Lock() + defer lock.Unlock() + if singleInMemorySwarm == nil { + singleInMemorySwarm = &single{ + memoryMock: make(map[string][]byte)} + } + } + return singleInMemorySwarm +} + +func getMemory() map[string][]byte { + ch := make(chan *single) + go func() { + ch <- getInMemorySwarm() + }() + mem := <-ch + return mem.memoryMock +} + +type memoryKeyValueStore struct { +} + +func (m *memoryKeyValueStore) Get(rootHash swarm.Address, key []byte) ([]byte, error) { + mem := getMemory() + val := mem[hex.EncodeToString(key)] + return val, nil +} + +func (m *memoryKeyValueStore) Put(rootHash swarm.Address, key []byte, value []byte) (swarm.Address, error) { + mem := getMemory() + mem[hex.EncodeToString(key)] = value + return swarm.EmptyAddress, nil +} + +func NewMemoryKeyValueStore() (MemoryKeyValueStore, error) { + return &memoryKeyValueStore{}, nil +} From 9c5c959396488679a0b7635901ef20b4baf99063 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A1lint=20Ujv=C3=A1ri?= <58116288+bosi95@users.noreply.github.com> Date: Tue, 26 Mar 2024 10:53:35 +0100 Subject: [PATCH 14/34] Session refactor (#24) * pr comment fix * add comment to session.NewFromKeystore --- pkg/dynamicaccess/session.go | 9 ++++-- pkg/dynamicaccess/session_test.go | 51 ++++++++++++++++++++++++++++--- 2 files changed, 53 insertions(+), 7 deletions(-) diff --git a/pkg/dynamicaccess/session.go b/pkg/dynamicaccess/session.go index 9d7634ffd01..68cea8ccbfb 100644 --- a/pkg/dynamicaccess/session.go +++ b/pkg/dynamicaccess/session.go @@ -21,11 +21,15 @@ type session struct { } func (s *session) Key(publicKey *ecdsa.PublicKey, nonces [][]byte) ([][]byte, error) { - x, _ := publicKey.Curve.ScalarMult(publicKey.X, publicKey.Y, s.key.D.Bytes()) - if x == nil { + x, y := publicKey.Curve.ScalarMult(publicKey.X, publicKey.Y, s.key.D.Bytes()) + if x == nil || y == nil { return nil, errors.New("shared secret is point at infinity") } + if len(nonces) == 0 { + return [][]byte{(*x).Bytes()}, nil + } + keys := make([][]byte, 0, len(nonces)) for _, nonce := range nonces { key, err := crypto.LegacyKeccak256(append(x.Bytes(), nonce...)) @@ -44,6 +48,7 @@ func NewDefaultSession(key *ecdsa.PrivateKey) Session { } } +// Currently implemented only in mock/session.go func NewFromKeystore(ks keystore.Service, tag, password string) Session { return nil } diff --git a/pkg/dynamicaccess/session_test.go b/pkg/dynamicaccess/session_test.go index 0cfee7691da..501d1abd2b6 100644 --- a/pkg/dynamicaccess/session_test.go +++ b/pkg/dynamicaccess/session_test.go @@ -52,9 +52,11 @@ func TestSessionKey(t *testing.T) { } si2 := dynamicaccess.NewDefaultSession(key2) - nonces := make([][]byte, 1) - if _, err := io.ReadFull(rand.Reader, nonces[0]); err != nil { - t.Fatal(err) + nonces := make([][]byte, 2) + for i := range nonces { + if _, err := io.ReadFull(rand.Reader, nonces[i]); err != nil { + t.Fatal(err) + } } keys1, err := si1.Key(&key2.PublicKey, nonces) @@ -66,6 +68,38 @@ func TestSessionKey(t *testing.T) { t.Fatal(err) } + if !bytes.Equal(keys1[0], keys2[0]) { + t.Fatalf("shared secrets do not match %s, %s", hex.EncodeToString(keys1[0]), hex.EncodeToString(keys2[0])) + } + if !bytes.Equal(keys1[1], keys2[1]) { + t.Fatalf("shared secrets do not match %s, %s", hex.EncodeToString(keys1[0]), hex.EncodeToString(keys2[0])) + } +} + +func TestSessionKeyWithoutNonces(t *testing.T) { + t.Parallel() + + key1, err := crypto.GenerateSecp256k1Key() + if err != nil { + t.Fatal(err) + } + si1 := dynamicaccess.NewDefaultSession(key1) + + key2, err := crypto.GenerateSecp256k1Key() + if err != nil { + t.Fatal(err) + } + si2 := dynamicaccess.NewDefaultSession(key2) + + keys1, err := si1.Key(&key2.PublicKey, nil) + if err != nil { + t.Fatal(err) + } + keys2, err := si2.Key(&key1.PublicKey, nil) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(keys1[0], keys2[0]) { t.Fatalf("shared secrets do not match %s, %s", hex.EncodeToString(keys1[0]), hex.EncodeToString(keys2[0])) } @@ -81,6 +115,7 @@ func TestSessionKeyFromKeystore(t *testing.T) { password2 := "password2" si1 := mock.NewFromKeystore(ks, tag1, password1, mockKeyFunc) + // si1 := dynamicaccess.NewFromKeystore(ks, tag1, password1) exists, err := ks.Exists(tag1) if err != nil { t.Fatal(err) @@ -97,6 +132,7 @@ func TestSessionKeyFromKeystore(t *testing.T) { } si2 := mock.NewFromKeystore(ks, tag2, password2, mockKeyFunc) + // si2 := dynamicaccess.NewFromKeystore(ks, tag2, password2) exists, err = ks.Exists(tag2) if err != nil { t.Fatal(err) @@ -113,8 +149,10 @@ func TestSessionKeyFromKeystore(t *testing.T) { } nonces := make([][]byte, 1) - if _, err := io.ReadFull(rand.Reader, nonces[0]); err != nil { - t.Fatal(err) + for i := range nonces { + if _, err := io.ReadFull(rand.Reader, nonces[i]); err != nil { + t.Fatal(err) + } } keys1, err := si1.Key(&key2.PublicKey, nonces) @@ -129,4 +167,7 @@ func TestSessionKeyFromKeystore(t *testing.T) { if !bytes.Equal(keys1[0], keys2[0]) { t.Fatalf("shared secrets do not match %s, %s", hex.EncodeToString(keys1[0]), hex.EncodeToString(keys2[0])) } + // if !bytes.Equal(keys1[1], keys2[1]) { + // t.Fatalf("shared secrets do not match %s, %s", hex.EncodeToString(keys1[0]), hex.EncodeToString(keys2[0])) + // } } From a5151aeea03fc6590f140c8448301076cbd90723 Mon Sep 17 00:00:00 2001 From: rolandlor <33499567+rolandlor@users.noreply.github.com> Date: Tue, 26 Mar 2024 16:48:22 +0100 Subject: [PATCH 15/34] Access logic refactor (#25) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refactors access logic --------- Co-authored-by: Peter Ott Co-authored-by: Ferenc Sárai Co-authored-by: Bálint Ujvári Co-authored-by: Peter Ott --- pkg/dynamicaccess/accesslogic.go | 121 +++++++++++--------------- pkg/dynamicaccess/accesslogic_test.go | 25 ++---- pkg/dynamicaccess/controller.go | 2 +- pkg/dynamicaccess/grantee.go | 23 +++-- pkg/dynamicaccess/grantee_manager.go | 10 +-- pkg/dynamicaccess/grantee_test.go | 18 ++-- pkg/dynamicaccess/session.go | 3 + 7 files changed, 90 insertions(+), 112 deletions(-) diff --git a/pkg/dynamicaccess/accesslogic.go b/pkg/dynamicaccess/accesslogic.go index 4b5abcd739e..591c9557a36 100644 --- a/pkg/dynamicaccess/accesslogic.go +++ b/pkg/dynamicaccess/accesslogic.go @@ -2,7 +2,6 @@ package dynamicaccess import ( "crypto/ecdsa" - "fmt" encryption "github.com/ethersphere/bee/pkg/encryption" "github.com/ethersphere/bee/pkg/swarm" @@ -11,44 +10,44 @@ import ( var hashFunc = sha3.NewLegacyKeccak256 -// Logic has the responsibility to return a ref for a given grantee and create new encrypted reference for a grantee -type Logic interface { +// Read-only interface for the ACT +type Decryptor interface { + // DecryptRef will return a decrypted reference, for given encrypted reference and grantee + DecryptRef(rootHash swarm.Address, encryped_ref swarm.Address, publisher *ecdsa.PublicKey) (swarm.Address, error) + // Embedding the Session interface + Session +} + +// Control interface for the ACT (does write operations) +type Control interface { + // Embedding the Decryptor interface + Decryptor // Adds a new grantee to the ACT - AddNewGranteeToContent(rootHash swarm.Address, publisherPubKey, granteePubKey *ecdsa.PublicKey) (swarm.Address, error) - // Get will return a decrypted reference, for given encrypted reference and grantee - Get(rootHash swarm.Address, encryped_ref swarm.Address, publisher *ecdsa.PublicKey) (swarm.Address, error) + AddGrantee(rootHash swarm.Address, publisherPubKey, granteePubKey *ecdsa.PublicKey, accessKey *encryption.Key) (swarm.Address, error) + // Encrypts a Swarm reference for a given grantee + EncryptRef(rootHash swarm.Address, grantee *ecdsa.PublicKey, ref swarm.Address) (swarm.Address, error) } type ActLogic struct { - session Session - act Act + Session + act Act } -var _ Logic = (*ActLogic)(nil) +var _ Decryptor = (*ActLogic)(nil) // Adds a new publisher to an empty act func (al ActLogic) AddPublisher(rootHash swarm.Address, publisher *ecdsa.PublicKey) (swarm.Address, error) { accessKey := encryption.GenerateRandomKey(encryption.KeyLength) - keys, err := al.getKeys(publisher) - if err != nil { - return swarm.EmptyAddress, err - } - lookupKey := keys[0] - accessKeyEncryptionKey := keys[1] - - accessKeyCipher := encryption.New(encryption.Key(accessKeyEncryptionKey), 0, uint32(0), hashFunc) - encryptedAccessKey, err := accessKeyCipher.Encrypt([]byte(accessKey)) - if err != nil { - return swarm.EmptyAddress, err - } - - return al.act.Add(rootHash, lookupKey, encryptedAccessKey) + return al.AddGrantee(rootHash, publisher, publisher, &accessKey) } // Encrypts a SWARM reference for a publisher func (al ActLogic) EncryptRef(rootHash swarm.Address, publisherPubKey *ecdsa.PublicKey, ref swarm.Address) (swarm.Address, error) { - accessKey := al.getAccessKey(rootHash, publisherPubKey) + accessKey, err := al.getAccessKey(rootHash, publisherPubKey) + if err != nil { + return swarm.EmptyAddress, err + } refCipher := encryption.New(accessKey, 0, uint32(0), hashFunc) encryptedRef, _ := refCipher.Encrypt(ref.Bytes()) @@ -56,9 +55,20 @@ func (al ActLogic) EncryptRef(rootHash swarm.Address, publisherPubKey *ecdsa.Pub } // Adds a new grantee to the ACT -func (al ActLogic) AddNewGranteeToContent(rootHash swarm.Address, publisherPubKey, granteePubKey *ecdsa.PublicKey) (swarm.Address, error) { - // Get previously generated access key - accessKey := al.getAccessKey(rootHash, publisherPubKey) +func (al ActLogic) AddGrantee(rootHash swarm.Address, publisherPubKey, granteePubKey *ecdsa.PublicKey, accessKeyPointer *encryption.Key) (swarm.Address, error) { + var accessKey encryption.Key + var err error // Declare the "err" variable + + if accessKeyPointer == nil { + // Get previously generated access key + accessKey, err = al.getAccessKey(rootHash, publisherPubKey) + if err != nil { + return swarm.EmptyAddress, err + } + } else { + // This is a newly created access key, because grantee is publisher (they are the same) + accessKey = *accessKeyPointer + } // Encrypt the access key for the new Grantee keys, err := al.getKeys(granteePubKey) @@ -77,62 +87,37 @@ func (al ActLogic) AddNewGranteeToContent(rootHash swarm.Address, publisherPubKe // Add the new encrypted access key for the Act return al.act.Add(rootHash, lookupKey, granteeEncryptedAccessKey) - } // Will return the access key for a publisher (public key) -func (al *ActLogic) getAccessKey(rootHash swarm.Address, publisherPubKey *ecdsa.PublicKey) []byte { +func (al *ActLogic) getAccessKey(rootHash swarm.Address, publisherPubKey *ecdsa.PublicKey) ([]byte, error) { keys, err := al.getKeys(publisherPubKey) if err != nil { - return nil + return nil, err } publisherLookupKey := keys[0] publisherAKDecryptionKey := keys[1] - + // no need to constructor call if value not found in act accessKeyDecryptionCipher := encryption.New(encryption.Key(publisherAKDecryptionKey), 0, uint32(0), hashFunc) - encryptedAK, err := al.getEncryptedAccessKey(rootHash, publisherLookupKey) + encryptedAK, err := al.act.Lookup(rootHash, publisherLookupKey) if err != nil { - return nil + return nil, err } - accessKey, err := accessKeyDecryptionCipher.Decrypt(encryptedAK) - if err != nil { - return nil - } + return accessKeyDecryptionCipher.Decrypt(encryptedAK) - return accessKey } -func (al *ActLogic) getKeys(publicKey *ecdsa.PublicKey) ([][]byte, error) { - // Generate lookup key and access key decryption - oneByteArray := []byte{1} - zeroByteArray := []byte{0} - - keys, err := al.session.Key(publicKey, [][]byte{zeroByteArray, oneByteArray}) - if err != nil { - return nil, err - } - return keys, nil -} +var oneByteArray = []byte{1} +var zeroByteArray = []byte{0} -// Gets the encrypted access key for a given grantee -func (al *ActLogic) getEncryptedAccessKey(rootHash swarm.Address, lookup_key []byte) ([]byte, error) { - val, err := al.act.Lookup(rootHash, lookup_key) - if err != nil { - return nil, err - } - return val, nil +// Generate lookup key and access key decryption key for a given public key +func (al *ActLogic) getKeys(publicKey *ecdsa.PublicKey) ([][]byte, error) { + return al.Session.Key(publicKey, [][]byte{zeroByteArray, oneByteArray}) } -// Get will return a decrypted reference, for given encrypted reference and grantee -func (al ActLogic) Get(rootHash swarm.Address, encryped_ref swarm.Address, grantee *ecdsa.PublicKey) (swarm.Address, error) { - if encryped_ref.Compare(swarm.EmptyAddress) == 0 { - return swarm.EmptyAddress, fmt.Errorf("encrypted ref not provided") - } - if grantee == nil { - return swarm.EmptyAddress, fmt.Errorf("grantee not provided") - } - +// DecryptRef will return a decrypted reference, for given encrypted reference and grantee +func (al ActLogic) DecryptRef(rootHash swarm.Address, encryped_ref swarm.Address, grantee *ecdsa.PublicKey) (swarm.Address, error) { keys, err := al.getKeys(grantee) if err != nil { return swarm.EmptyAddress, err @@ -141,7 +126,7 @@ func (al ActLogic) Get(rootHash swarm.Address, encryped_ref swarm.Address, grant accessKeyDecryptionKey := keys[1] // Lookup encrypted access key from the ACT manifest - encryptedAccessKey, err := al.getEncryptedAccessKey(rootHash, lookupKey) + encryptedAccessKey, err := al.act.Lookup(rootHash, lookupKey) if err != nil { return swarm.EmptyAddress, err } @@ -163,9 +148,9 @@ func (al ActLogic) Get(rootHash swarm.Address, encryped_ref swarm.Address, grant return swarm.NewAddress(ref), nil } -func NewLogic(s Session, act Act) ActLogic { +func NewLogic(S Session, act Act) ActLogic { return ActLogic{ - session: s, + Session: S, act: act, } } diff --git a/pkg/dynamicaccess/accesslogic_test.go b/pkg/dynamicaccess/accesslogic_test.go index b87bac84ffd..1a6da8af80b 100644 --- a/pkg/dynamicaccess/accesslogic_test.go +++ b/pkg/dynamicaccess/accesslogic_test.go @@ -39,7 +39,7 @@ func generateFixPrivateKey(input int64) ecdsa.PrivateKey { return privateKey } -func TestGet_Success(t *testing.T) { +func TestDecryptRef_Success(t *testing.T) { id0 := generateFixPrivateKey(0) var mockStorer = mockstorer.New() act := dynamicaccess.NewInManifestAct(mockStorer) @@ -61,7 +61,7 @@ func TestGet_Success(t *testing.T) { t.Error(err) } - acutalRef, err := al.Get(ref, encryptedRef, &id0.PublicKey) + acutalRef, err := al.DecryptRef(ref, encryptedRef, &id0.PublicKey) if err != nil { t.Errorf("There was an error while calling Get: ") t.Error(err) @@ -73,8 +73,7 @@ func TestGet_Success(t *testing.T) { } } -// This test function tests those cases where different parameters are missing -func TestGet_Error(t *testing.T) { +func TestDecryptRef_Error(t *testing.T) { id0 := generateFixPrivateKey(0) act := dynamicaccess.NewInMemoryAct() @@ -88,21 +87,11 @@ func TestGet_Error(t *testing.T) { encryptedRef, _ := al.EncryptRef(ref, &id0.PublicKey, swarm.NewAddress([]byte(expectedRef))) - r, err := al.Get(swarm.RandAddress(t), encryptedRef, &id0.PublicKey) + r, err := al.DecryptRef(swarm.RandAddress(t), encryptedRef, nil) if err == nil { t.Logf("r: %s", r.String()) t.Errorf("Get should give back encrypted access key not found error!") } - - refTwo, _ := al.Get(swarm.RandAddress(t), swarm.EmptyAddress, &id0.PublicKey) - if swarm.EmptyAddress.Compare(refTwo) != 0 { - t.Errorf("Get should give back empty string if encrypted ref not provided!") - } - - _, err = al.Get(swarm.RandAddress(t), encryptedRef, nil) - if err == nil { - t.Errorf("Get should give back error if grantee not provided!") - } } func TestAddPublisher(t *testing.T) { @@ -136,7 +125,7 @@ func TestAddPublisher(t *testing.T) { } } -func TestAdd_New_Grantee_To_Content(t *testing.T) { +func TestAddNewGranteeToContent(t *testing.T) { id0 := generateFixPrivateKey(0) id1 := generateFixPrivateKey(1) @@ -153,12 +142,12 @@ func TestAdd_New_Grantee_To_Content(t *testing.T) { t.Errorf("AddNewGrantee: expected no error, got %v", err) } - ref, err = al.AddNewGranteeToContent(ref, &id0.PublicKey, &id1.PublicKey) + ref, err = al.AddGrantee(ref, &id0.PublicKey, &id1.PublicKey, nil) if err != nil { t.Errorf("AddNewGrantee: expected no error, got %v", err) } - ref, err = al.AddNewGranteeToContent(ref, &id0.PublicKey, &id2.PublicKey) + ref, err = al.AddGrantee(ref, &id0.PublicKey, &id2.PublicKey, nil) if err != nil { t.Errorf("AddNewGrantee: expected no error, got %v", err) } diff --git a/pkg/dynamicaccess/controller.go b/pkg/dynamicaccess/controller.go index 9ced8a4d873..b1c6e306934 100644 --- a/pkg/dynamicaccess/controller.go +++ b/pkg/dynamicaccess/controller.go @@ -22,7 +22,7 @@ func (c *defaultController) DownloadHandler(timestamp int64, enryptedRef swarm.A if err != nil { return swarm.EmptyAddress, err } - addr, err := c.accessLogic.Get(swarm.EmptyAddress, enryptedRef, publisher) + addr, err := c.accessLogic.DecryptRef(swarm.EmptyAddress, enryptedRef, publisher) return addr, err } diff --git a/pkg/dynamicaccess/grantee.go b/pkg/dynamicaccess/grantee.go index dfd7015e49f..d85ebf217ea 100644 --- a/pkg/dynamicaccess/grantee.go +++ b/pkg/dynamicaccess/grantee.go @@ -4,29 +4,29 @@ import ( "crypto/ecdsa" ) -type Grantee interface { - AddGrantees(topic string, addList []*ecdsa.PublicKey) error - RemoveGrantees(topic string, removeList []*ecdsa.PublicKey) error - GetGrantees(topic string) []*ecdsa.PublicKey +type GranteeList interface { + Add(topic string, addList []*ecdsa.PublicKey) error + Remove(topic string, removeList []*ecdsa.PublicKey) error + Get(topic string) []*ecdsa.PublicKey } -type defaultGrantee struct { +type GranteeListStruct struct { grantees map[string][]*ecdsa.PublicKey } -func (g *defaultGrantee) GetGrantees(topic string) []*ecdsa.PublicKey { +func (g *GranteeListStruct) Get(topic string) []*ecdsa.PublicKey { grantees := g.grantees[topic] keys := make([]*ecdsa.PublicKey, len(grantees)) copy(keys, grantees) return keys } -func (g *defaultGrantee) AddGrantees(topic string, addList []*ecdsa.PublicKey) error { +func (g *GranteeListStruct) Add(topic string, addList []*ecdsa.PublicKey) error { g.grantees[topic] = append(g.grantees[topic], addList...) return nil } -func (g *defaultGrantee) RemoveGrantees(topic string, removeList []*ecdsa.PublicKey) error { +func (g *GranteeListStruct) Remove(topic string, removeList []*ecdsa.PublicKey) error { for _, remove := range removeList { for i, grantee := range g.grantees[topic] { if *grantee == *remove { @@ -36,10 +36,9 @@ func (g *defaultGrantee) RemoveGrantees(topic string, removeList []*ecdsa.Public } } - return nil } -func NewGrantee() Grantee { - return &defaultGrantee{grantees: make(map[string][]*ecdsa.PublicKey)} -} \ No newline at end of file +func NewGrantee() *GranteeListStruct { + return &GranteeListStruct{grantees: make(map[string][]*ecdsa.PublicKey)} +} diff --git a/pkg/dynamicaccess/grantee_manager.go b/pkg/dynamicaccess/grantee_manager.go index e6884f77bea..0627db9794a 100644 --- a/pkg/dynamicaccess/grantee_manager.go +++ b/pkg/dynamicaccess/grantee_manager.go @@ -21,7 +21,7 @@ var _ GranteeManager = (*granteeManager)(nil) type granteeManager struct { accessLogic ActLogic - granteeList Grantee + granteeList GranteeList } func NewGranteeManager(al ActLogic) *granteeManager { @@ -29,17 +29,17 @@ func NewGranteeManager(al ActLogic) *granteeManager { } func (gm *granteeManager) Get(topic string) []*ecdsa.PublicKey { - return gm.granteeList.GetGrantees(topic) + return gm.granteeList.Get(topic) } func (gm *granteeManager) Add(topic string, addList []*ecdsa.PublicKey) error { - return gm.granteeList.AddGrantees(topic, addList) + return gm.granteeList.Add(topic, addList) } func (gm *granteeManager) Publish(rootHash swarm.Address, publisher *ecdsa.PublicKey, topic string) (swarm.Address, error) { ref, err := gm.accessLogic.AddPublisher(rootHash, publisher) - for _, grantee := range gm.granteeList.GetGrantees(topic) { - ref, err = gm.accessLogic.AddNewGranteeToContent(ref, publisher, grantee) + for _, grantee := range gm.granteeList.Get(topic) { + ref, err = gm.accessLogic.AddGrantee(ref, publisher, grantee, nil) } return ref, err } diff --git a/pkg/dynamicaccess/grantee_test.go b/pkg/dynamicaccess/grantee_test.go index 7418a741d77..7962690ccb4 100644 --- a/pkg/dynamicaccess/grantee_test.go +++ b/pkg/dynamicaccess/grantee_test.go @@ -10,6 +10,8 @@ import ( "github.com/ethersphere/bee/pkg/dynamicaccess" ) +var _ dynamicaccess.GranteeList = (*dynamicaccess.GranteeListStruct)(nil) + func TestGranteeAddGrantees(t *testing.T) { grantee := dynamicaccess.NewGrantee() @@ -25,13 +27,13 @@ func TestGranteeAddGrantees(t *testing.T) { addList := []*ecdsa.PublicKey{&key1.PublicKey, &key2.PublicKey} exampleTopic := "topic" - err = grantee.AddGrantees(exampleTopic, addList) + err = grantee.Add(exampleTopic, addList) if err != nil { t.Errorf("Expected no error, got %v", err) } - grantees := grantee.GetGrantees(exampleTopic) + grantees := grantee.Get(exampleTopic) if !reflect.DeepEqual(grantees, addList) { t.Errorf("Expected grantees %v, got %v", addList, grantees) } @@ -52,18 +54,18 @@ func TestRemoveGrantees(t *testing.T) { addList := []*ecdsa.PublicKey{&key1.PublicKey, &key2.PublicKey} exampleTopic := "topic" - err = grantee.AddGrantees(exampleTopic, addList) + err = grantee.Add(exampleTopic, addList) if err != nil { t.Errorf("Expected no error, got %v", err) } removeList := []*ecdsa.PublicKey{&key1.PublicKey} - err = grantee.RemoveGrantees(exampleTopic, removeList) + err = grantee.Remove(exampleTopic, removeList) if err != nil { t.Errorf("Expected no error, got %v", err) } - grantees := grantee.GetGrantees(exampleTopic) + grantees := grantee.Get(exampleTopic) expectedGrantees := []*ecdsa.PublicKey{&key2.PublicKey} for i, grantee := range grantees { @@ -88,15 +90,15 @@ func TestGetGrantees(t *testing.T) { addList := []*ecdsa.PublicKey{&key1.PublicKey, &key2.PublicKey} exampleTopic := "topic" - err = grantee.AddGrantees(exampleTopic, addList) + err = grantee.Add(exampleTopic, addList) if err != nil { t.Errorf("Expected no error, got %v", err) } - grantees := grantee.GetGrantees(exampleTopic) + grantees := grantee.Get(exampleTopic) for i, grantee := range grantees { if grantee != addList[i] { t.Errorf("Expected grantee %v, got %v", addList[i], grantee) } } -} \ No newline at end of file +} diff --git a/pkg/dynamicaccess/session.go b/pkg/dynamicaccess/session.go index 68cea8ccbfb..b2718aedd67 100644 --- a/pkg/dynamicaccess/session.go +++ b/pkg/dynamicaccess/session.go @@ -21,6 +21,9 @@ type session struct { } func (s *session) Key(publicKey *ecdsa.PublicKey, nonces [][]byte) ([][]byte, error) { + if publicKey == nil { + return nil, errors.New("invalid public key") + } x, y := publicKey.Curve.ScalarMult(publicKey.X, publicKey.Y, s.key.D.Bytes()) if x == nil || y == nil { return nil, errors.New("shared secret is point at infinity") From 2e843ea19bb799949e77fcc4d8ed3650cf80ac1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferenc=20S=C3=A1rai?= Date: Wed, 27 Mar 2024 09:00:12 +0100 Subject: [PATCH 16/34] (refactor:) PR comments (#23) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * grantee-refactor * Dried up code, related to AddPublisher - AddNewGranteeToContent * Refactor * removed getEncryptedAccessKey * Renamed AddGrentees, RemoveGrantees, etc to Add, Remove, etc * (refactor:) PR comments * (refactor:) compile check * removed encrypted_ref, grantee check (validation) * changed interface * comments * some more comments * refactor kvs and add load and store * (refactor:) Use ref * renamed defaultGrantee to granteeList * removed null encrypted test in in TestGet_Error * refactor kvs: pass kvs IF argument instead of storing it * Refactor according to the result of the workshop * refactor kvs IF and mock * fix merge errors and Logic/get_error test * (test:) Add test for put/get after kvs.Save --------- Co-authored-by: Roland Seres Co-authored-by: Peter Ott Co-authored-by: Ferenc Sárai Co-authored-by: Bálint Ujvári Co-authored-by: Peter Ott --- pkg/dynamicaccess/accesslogic.go | 39 ++++--- pkg/dynamicaccess/accesslogic_test.go | 81 +++++-------- pkg/dynamicaccess/act.go | 65 ----------- pkg/dynamicaccess/act_test.go | 106 ----------------- pkg/dynamicaccess/controller.go | 18 +-- pkg/dynamicaccess/controller_test.go | 7 +- pkg/dynamicaccess/grantee_manager.go | 11 +- pkg/dynamicaccess/grantee_manager_test.go | 8 +- pkg/dynamicaccess/history.go | 17 +-- pkg/dynamicaccess/history_test.go | 28 ++--- pkg/dynamicaccess/mock/act.go | 56 --------- pkg/dynamicaccess/mock/history.go | 14 +-- pkg/kvs/kvs.go | 80 +++++++++---- pkg/kvs/kvs_test.go | 132 ++++++++++++++++++++++ pkg/kvs/manifest/kvs.go | 86 -------------- pkg/kvs/{memory => mock}/kvs.go | 31 +++-- 16 files changed, 304 insertions(+), 475 deletions(-) delete mode 100644 pkg/dynamicaccess/act.go delete mode 100644 pkg/dynamicaccess/act_test.go delete mode 100644 pkg/dynamicaccess/mock/act.go create mode 100644 pkg/kvs/kvs_test.go delete mode 100644 pkg/kvs/manifest/kvs.go rename pkg/kvs/{memory => mock}/kvs.go (55%) diff --git a/pkg/dynamicaccess/accesslogic.go b/pkg/dynamicaccess/accesslogic.go index 591c9557a36..ad33ba5eb16 100644 --- a/pkg/dynamicaccess/accesslogic.go +++ b/pkg/dynamicaccess/accesslogic.go @@ -4,6 +4,7 @@ import ( "crypto/ecdsa" encryption "github.com/ethersphere/bee/pkg/encryption" + "github.com/ethersphere/bee/pkg/kvs" "github.com/ethersphere/bee/pkg/swarm" "golang.org/x/crypto/sha3" ) @@ -13,7 +14,7 @@ var hashFunc = sha3.NewLegacyKeccak256 // Read-only interface for the ACT type Decryptor interface { // DecryptRef will return a decrypted reference, for given encrypted reference and grantee - DecryptRef(rootHash swarm.Address, encryped_ref swarm.Address, publisher *ecdsa.PublicKey) (swarm.Address, error) + DecryptRef(storage kvs.KeyValueStore, encryped_ref swarm.Address, publisher *ecdsa.PublicKey) (swarm.Address, error) // Embedding the Session interface Session } @@ -23,28 +24,27 @@ type Control interface { // Embedding the Decryptor interface Decryptor // Adds a new grantee to the ACT - AddGrantee(rootHash swarm.Address, publisherPubKey, granteePubKey *ecdsa.PublicKey, accessKey *encryption.Key) (swarm.Address, error) + AddGrantee(storage kvs.KeyValueStore, publisherPubKey, granteePubKey *ecdsa.PublicKey, accessKey *encryption.Key) error // Encrypts a Swarm reference for a given grantee - EncryptRef(rootHash swarm.Address, grantee *ecdsa.PublicKey, ref swarm.Address) (swarm.Address, error) + EncryptRef(storage kvs.KeyValueStore, grantee *ecdsa.PublicKey, ref swarm.Address) error } type ActLogic struct { Session - act Act } var _ Decryptor = (*ActLogic)(nil) // Adds a new publisher to an empty act -func (al ActLogic) AddPublisher(rootHash swarm.Address, publisher *ecdsa.PublicKey) (swarm.Address, error) { +func (al ActLogic) AddPublisher(storage kvs.KeyValueStore, publisher *ecdsa.PublicKey) error { accessKey := encryption.GenerateRandomKey(encryption.KeyLength) - return al.AddGrantee(rootHash, publisher, publisher, &accessKey) + return al.AddGrantee(storage, publisher, publisher, &accessKey) } // Encrypts a SWARM reference for a publisher -func (al ActLogic) EncryptRef(rootHash swarm.Address, publisherPubKey *ecdsa.PublicKey, ref swarm.Address) (swarm.Address, error) { - accessKey, err := al.getAccessKey(rootHash, publisherPubKey) +func (al ActLogic) EncryptRef(storage kvs.KeyValueStore, publisherPubKey *ecdsa.PublicKey, ref swarm.Address) (swarm.Address, error) { + accessKey, err := al.getAccessKey(storage, publisherPubKey) if err != nil { return swarm.EmptyAddress, err } @@ -55,15 +55,15 @@ func (al ActLogic) EncryptRef(rootHash swarm.Address, publisherPubKey *ecdsa.Pub } // Adds a new grantee to the ACT -func (al ActLogic) AddGrantee(rootHash swarm.Address, publisherPubKey, granteePubKey *ecdsa.PublicKey, accessKeyPointer *encryption.Key) (swarm.Address, error) { +func (al ActLogic) AddGrantee(storage kvs.KeyValueStore, publisherPubKey, granteePubKey *ecdsa.PublicKey, accessKeyPointer *encryption.Key) error { var accessKey encryption.Key var err error // Declare the "err" variable if accessKeyPointer == nil { // Get previously generated access key - accessKey, err = al.getAccessKey(rootHash, publisherPubKey) + accessKey, err = al.getAccessKey(storage, publisherPubKey) if err != nil { - return swarm.EmptyAddress, err + return err } } else { // This is a newly created access key, because grantee is publisher (they are the same) @@ -73,7 +73,7 @@ func (al ActLogic) AddGrantee(rootHash swarm.Address, publisherPubKey, granteePu // Encrypt the access key for the new Grantee keys, err := al.getKeys(granteePubKey) if err != nil { - return swarm.EmptyAddress, err + return err } lookupKey := keys[0] accessKeyEncryptionKey := keys[1] @@ -82,15 +82,15 @@ func (al ActLogic) AddGrantee(rootHash swarm.Address, publisherPubKey, granteePu cipher := encryption.New(encryption.Key(accessKeyEncryptionKey), 0, uint32(0), hashFunc) granteeEncryptedAccessKey, err := cipher.Encrypt(accessKey) if err != nil { - return swarm.EmptyAddress, err + return err } // Add the new encrypted access key for the Act - return al.act.Add(rootHash, lookupKey, granteeEncryptedAccessKey) + return storage.Put(lookupKey, granteeEncryptedAccessKey) } // Will return the access key for a publisher (public key) -func (al *ActLogic) getAccessKey(rootHash swarm.Address, publisherPubKey *ecdsa.PublicKey) ([]byte, error) { +func (al *ActLogic) getAccessKey(storage kvs.KeyValueStore, publisherPubKey *ecdsa.PublicKey) ([]byte, error) { keys, err := al.getKeys(publisherPubKey) if err != nil { return nil, err @@ -99,7 +99,7 @@ func (al *ActLogic) getAccessKey(rootHash swarm.Address, publisherPubKey *ecdsa. publisherAKDecryptionKey := keys[1] // no need to constructor call if value not found in act accessKeyDecryptionCipher := encryption.New(encryption.Key(publisherAKDecryptionKey), 0, uint32(0), hashFunc) - encryptedAK, err := al.act.Lookup(rootHash, publisherLookupKey) + encryptedAK, err := storage.Get(publisherLookupKey) if err != nil { return nil, err } @@ -117,7 +117,7 @@ func (al *ActLogic) getKeys(publicKey *ecdsa.PublicKey) ([][]byte, error) { } // DecryptRef will return a decrypted reference, for given encrypted reference and grantee -func (al ActLogic) DecryptRef(rootHash swarm.Address, encryped_ref swarm.Address, grantee *ecdsa.PublicKey) (swarm.Address, error) { +func (al ActLogic) DecryptRef(storage kvs.KeyValueStore, encryped_ref swarm.Address, grantee *ecdsa.PublicKey) (swarm.Address, error) { keys, err := al.getKeys(grantee) if err != nil { return swarm.EmptyAddress, err @@ -126,7 +126,7 @@ func (al ActLogic) DecryptRef(rootHash swarm.Address, encryped_ref swarm.Address accessKeyDecryptionKey := keys[1] // Lookup encrypted access key from the ACT manifest - encryptedAccessKey, err := al.act.Lookup(rootHash, lookupKey) + encryptedAccessKey, err := storage.Get(lookupKey) if err != nil { return swarm.EmptyAddress, err } @@ -148,9 +148,8 @@ func (al ActLogic) DecryptRef(rootHash swarm.Address, encryped_ref swarm.Address return swarm.NewAddress(ref), nil } -func NewLogic(S Session, act Act) ActLogic { +func NewLogic(S Session) ActLogic { return ActLogic{ Session: S, - act: act, } } diff --git a/pkg/dynamicaccess/accesslogic_test.go b/pkg/dynamicaccess/accesslogic_test.go index 1a6da8af80b..cb5ff0a6c78 100644 --- a/pkg/dynamicaccess/accesslogic_test.go +++ b/pkg/dynamicaccess/accesslogic_test.go @@ -8,15 +8,15 @@ import ( "testing" "github.com/ethersphere/bee/pkg/dynamicaccess" - mockstorer "github.com/ethersphere/bee/pkg/storer/mock" + kvsmock "github.com/ethersphere/bee/pkg/kvs/mock" "github.com/ethersphere/bee/pkg/swarm" ) // Generates a new test environment with a fix private key -func setupAccessLogic2(act dynamicaccess.Act) dynamicaccess.ActLogic { +func setupAccessLogic2() dynamicaccess.ActLogic { privateKey := generateFixPrivateKey(1000) diffieHellman := dynamicaccess.NewDefaultSession(&privateKey) - al := dynamicaccess.NewLogic(diffieHellman, act) + al := dynamicaccess.NewLogic(diffieHellman) return al } @@ -41,10 +41,9 @@ func generateFixPrivateKey(input int64) ecdsa.PrivateKey { func TestDecryptRef_Success(t *testing.T) { id0 := generateFixPrivateKey(0) - var mockStorer = mockstorer.New() - act := dynamicaccess.NewInManifestAct(mockStorer) - al := setupAccessLogic2(act) - ref, err := al.AddPublisher(swarm.EmptyAddress, &id0.PublicKey) + s := kvsmock.New() + al := setupAccessLogic2() + err := al.AddPublisher(s, &id0.PublicKey) if err != nil { t.Errorf("AddPublisher: expected no error, got %v", err) } @@ -54,14 +53,14 @@ func TestDecryptRef_Success(t *testing.T) { expectedRef := swarm.NewAddress(byteRef) t.Logf("encryptedRef: %s", expectedRef.String()) - encryptedRef, err := al.EncryptRef(ref, &id0.PublicKey, expectedRef) + encryptedRef, err := al.EncryptRef(s, &id0.PublicKey, expectedRef) t.Logf("encryptedRef: %s", encryptedRef.String()) if err != nil { t.Errorf("There was an error while calling EncryptRef: ") t.Error(err) } - acutalRef, err := al.DecryptRef(ref, encryptedRef, &id0.PublicKey) + acutalRef, err := al.DecryptRef(s, encryptedRef, &id0.PublicKey) if err != nil { t.Errorf("There was an error while calling Get: ") t.Error(err) @@ -76,18 +75,18 @@ func TestDecryptRef_Success(t *testing.T) { func TestDecryptRef_Error(t *testing.T) { id0 := generateFixPrivateKey(0) - act := dynamicaccess.NewInMemoryAct() - al := setupAccessLogic2(act) - ref, err := al.AddPublisher(swarm.EmptyAddress, &id0.PublicKey) + s := kvsmock.New() + al := setupAccessLogic2() + err := al.AddPublisher(s, &id0.PublicKey) if err != nil { t.Errorf("AddPublisher: expected no error, got %v", err) } expectedRef := "39a5ea87b141fe44aa609c3327ecd896c0e2122897f5f4bbacf74db1033c5559" - encryptedRef, _ := al.EncryptRef(ref, &id0.PublicKey, swarm.NewAddress([]byte(expectedRef))) + encryptedRef, _ := al.EncryptRef(s, &id0.PublicKey, swarm.NewAddress([]byte(expectedRef))) - r, err := al.DecryptRef(swarm.RandAddress(t), encryptedRef, nil) + r, err := al.DecryptRef(s, encryptedRef, nil) if err == nil { t.Logf("r: %s", r.String()) t.Errorf("Get should give back encrypted access key not found error!") @@ -97,9 +96,10 @@ func TestDecryptRef_Error(t *testing.T) { func TestAddPublisher(t *testing.T) { id0 := generateFixPrivateKey(0) savedLookupKey := "bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a" - act := dynamicaccess.NewInMemoryAct() - al := setupAccessLogic2(act) - ref, err := al.AddPublisher(swarm.EmptyAddress, &id0.PublicKey) + s := kvsmock.New() + + al := setupAccessLogic2() + err := al.AddPublisher(s, &id0.PublicKey) if err != nil { t.Errorf("AddPublisher: expected no error, got %v", err) } @@ -109,7 +109,7 @@ func TestAddPublisher(t *testing.T) { t.Errorf("DecodeString: expected no error, got %v", err) } - encryptedAccessKey, err := act.Lookup(ref, decodedSavedLookupKey) + encryptedAccessKey, err := s.Get(decodedSavedLookupKey) if err != nil { t.Errorf("Lookup: expected no error, got %v", err) } @@ -120,7 +120,7 @@ func TestAddPublisher(t *testing.T) { if len(decodedEncryptedAccessKey) != 64 { t.Errorf("AddPublisher: expected encrypted access key length 64, got %d", len(decodedEncryptedAccessKey)) } - if act == nil { + if s == nil { t.Errorf("AddPublisher: expected act, got nil") } } @@ -135,19 +135,19 @@ func TestAddNewGranteeToContent(t *testing.T) { firstAddedGranteeLookupKey := "e221a2abf64357260e8f2c937ee938aed98dce097e537c1a3fd4caf73510dbe4" secondAddedGranteeLookupKey := "8fe8dff7cd15a6a0095c1b25071a5691e7c901fd0b95857a96c0e4659b48716a" - act := dynamicaccess.NewInMemoryAct() - al := setupAccessLogic2(act) - ref, err := al.AddPublisher(swarm.EmptyAddress, &id0.PublicKey) + s := kvsmock.New() + al := setupAccessLogic2() + err := al.AddPublisher(s, &id0.PublicKey) if err != nil { t.Errorf("AddNewGrantee: expected no error, got %v", err) } - ref, err = al.AddGrantee(ref, &id0.PublicKey, &id1.PublicKey, nil) + err = al.AddGrantee(s, &id0.PublicKey, &id1.PublicKey, nil) if err != nil { t.Errorf("AddNewGrantee: expected no error, got %v", err) } - ref, err = al.AddGrantee(ref, &id0.PublicKey, &id2.PublicKey, nil) + err = al.AddGrantee(s, &id0.PublicKey, &id2.PublicKey, nil) if err != nil { t.Errorf("AddNewGrantee: expected no error, got %v", err) } @@ -156,7 +156,7 @@ func TestAddNewGranteeToContent(t *testing.T) { if err != nil { t.Errorf("AddNewGrantee: expected no error, got %v", err) } - result, _ := act.Lookup(ref, lookupKeyAsByte) + result, _ := s.Get(lookupKeyAsByte) hexEncodedEncryptedAK := hex.EncodeToString(result) if len(hexEncodedEncryptedAK) != 64 { t.Errorf("AddNewGrantee: expected encrypted access key length 64, got %d", len(hexEncodedEncryptedAK)) @@ -166,7 +166,7 @@ func TestAddNewGranteeToContent(t *testing.T) { if err != nil { t.Errorf("AddNewGrantee: expected no error, got %v", err) } - result, _ = act.Lookup(ref, lookupKeyAsByte) + result, _ = s.Get(lookupKeyAsByte) hexEncodedEncryptedAK = hex.EncodeToString(result) if len(hexEncodedEncryptedAK) != 64 { t.Errorf("AddNewGrantee: expected encrypted access key length 64, got %d", len(hexEncodedEncryptedAK)) @@ -176,36 +176,9 @@ func TestAddNewGranteeToContent(t *testing.T) { if err != nil { t.Errorf("AddNewGrantee: expected no error, got %v", err) } - result, _ = act.Lookup(ref, lookupKeyAsByte) + result, _ = s.Get(lookupKeyAsByte) hexEncodedEncryptedAK = hex.EncodeToString(result) if len(hexEncodedEncryptedAK) != 64 { t.Errorf("AddNewGrantee: expected encrypted access key length 64, got %d", len(hexEncodedEncryptedAK)) } } - -func TestEncryptRef(t *testing.T) { - ref := "39a5ea87b141fe44aa609c3327ecd896c0e2122897f5f4bbacf74db1033c5559" - savedEncryptedRef := "230cdcfb2e67adddb2822b38f70105213ab3e4f97d03560bfbfbb218f487c5303e9aa9a97e62aa1a8003f162679e7c65e1c8e3aacaec2043fd5d2a4a7d69285e" - - id0 := generateFixPrivateKey(0) - act := dynamicaccess.NewInMemoryAct() - al := setupAccessLogic2(act) - decodedLookupKey, err := hex.DecodeString("bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a") - if err != nil { - t.Errorf("EncryptRef: expected no error, got %v", err) - } - - addRef, err := act.Add(swarm.EmptyAddress, decodedLookupKey, []byte("42")) - if err != nil { - t.Errorf("Add: expected no error, got %v", err) - } - - encryptedRefValue, err := al.EncryptRef(addRef, &id0.PublicKey, swarm.NewAddress([]byte(ref))) - if err != nil { - t.Errorf("EncryptRef: expected no error, got %v", err) - } - - if encryptedRefValue.String() != savedEncryptedRef { - t.Errorf("EncryptRef: expected encrypted ref, got empty address") - } -} diff --git a/pkg/dynamicaccess/act.go b/pkg/dynamicaccess/act.go deleted file mode 100644 index f7595b5b1ec..00000000000 --- a/pkg/dynamicaccess/act.go +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright 2024 The Swarm Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package dynamicaccess - -import ( - "github.com/ethersphere/bee/pkg/api" - "github.com/ethersphere/bee/pkg/kvs" - kvsmanifest "github.com/ethersphere/bee/pkg/kvs/manifest" - kvsmemory "github.com/ethersphere/bee/pkg/kvs/memory" - "github.com/ethersphere/bee/pkg/swarm" -) - -// Act represents an interface for accessing and manipulating data. -type Act interface { - // Add adds a key-value pair to the data store. - Add(rootHash swarm.Address, key []byte, val []byte) (swarm.Address, error) - - // Lookup retrieves the value associated with the given key from the data store. - Lookup(rootHash swarm.Address, key []byte) ([]byte, error) - - // Load loads the data store from the given address. - //Load(addr swarm.Address) error - - // Store stores the current state of the data store and returns the address of the ACT. - //Store() (swarm.Address, error) -} - -// inKvsAct is an implementation of the Act interface that uses kvs storage. -type inKvsAct struct { - storage kvs.KeyValueStore -} - -// Add adds a key-value pair to the in-memory data store. -func (act *inKvsAct) Add(rootHash swarm.Address, key []byte, val []byte) (swarm.Address, error) { - return act.storage.Put(rootHash, key, val) -} - -// Lookup retrieves the value associated with the given key from the in-memory data store. -func (act *inKvsAct) Lookup(rootHash swarm.Address, key []byte) ([]byte, error) { - return act.storage.Get(rootHash, key) -} - -// NewInMemoryAct creates a new instance of the Act interface with in-memory storage. -func NewInMemoryAct() Act { - s, err := kvs.NewKeyValueStore(nil, kvsmemory.KvsTypeMemory) - if err != nil { - return nil - } - return &inKvsAct{ - storage: s, - } -} - -// NewInManifestAct creates a new instance of the Act interface with manifest storage. -func NewInManifestAct(storer api.Storer) Act { - s, err := kvs.NewKeyValueStore(storer, kvsmanifest.KvsTypeManifest) - if err != nil { - return nil - } - return &inKvsAct{ - storage: s, - } -} diff --git a/pkg/dynamicaccess/act_test.go b/pkg/dynamicaccess/act_test.go deleted file mode 100644 index 6522a76b9ed..00000000000 --- a/pkg/dynamicaccess/act_test.go +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright 2024 The Swarm Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package dynamicaccess_test - -import ( - "bytes" - "encoding/hex" - "testing" - - "github.com/ethersphere/bee/pkg/dynamicaccess" - mockstorer "github.com/ethersphere/bee/pkg/storer/mock" - "github.com/ethersphere/bee/pkg/swarm" -) - -type ActType int - -const ( - Memory ActType = iota - Manifest -) - -func (a ActType) String() string { - return [...]string{"Memory", "Manifest"}[a] -} - -var mockStorer = mockstorer.New() - -var acts = map[ActType]func() dynamicaccess.Act{ - Memory: dynamicaccess.NewInMemoryAct, - Manifest: func() dynamicaccess.Act { return dynamicaccess.NewInManifestAct(mockStorer) }, -} - -func TestActAddLookup(t *testing.T) { - for actType, actCreate := range acts { - t.Logf("Running test for ActType: %s", actType) - act := actCreate() - - lookupKey := swarm.RandAddress(t).Bytes() - encryptedAccesskey := swarm.RandAddress(t).Bytes() - - ref, err := act.Add(swarm.EmptyAddress, lookupKey, encryptedAccesskey) - if err != nil { - t.Errorf("Add() should not return an error: %v", err) - } - - key, err := act.Lookup(ref, lookupKey) - if err != nil { - t.Errorf("Lookup() should not return an error: %v", err) - } - - if !bytes.Equal(key, encryptedAccesskey) { - t.Errorf("Get() value is not the expected %s != %s", hex.EncodeToString(key), hex.EncodeToString(encryptedAccesskey)) - } - } -} - -func TestActAddLookupWithNew(t *testing.T) { - for actType, actCreate := range acts { - t.Logf("Running test for ActType: %s", actType) - act1 := actCreate() - lookupKey := swarm.RandAddress(t).Bytes() - encryptedAccesskey := swarm.RandAddress(t).Bytes() - - ref, err := act1.Add(swarm.EmptyAddress, lookupKey, encryptedAccesskey) - if err != nil { - t.Fatalf("Add() should not return an error: %v", err) - } - - act2 := actCreate() - key, err := act2.Lookup(ref, lookupKey) - if err != nil { - t.Fatalf("Lookup() should not return an error: %v", err) - } - - if !bytes.Equal(key, encryptedAccesskey) { - t.Errorf("Get() value is not the expected %s != %s", hex.EncodeToString(key), hex.EncodeToString(encryptedAccesskey)) - } - } -} - -/* -func TestActStoreLoad(t *testing.T) { - - act := dynamicaccess.NewInMemoryAct() - lookupKey := swarm.RandAddress(t).Bytes() - encryptedAccesskey := swarm.RandAddress(t).Bytes() - err := act.Add(lookupKey, encryptedAccesskey) - if err != nil { - t.Error("Add() should not return an error") - } - - swarm_ref, err := act.Store() - if err != nil { - t.Error("Store() should not return an error") - } - - actualAct := dynamicaccess.NewInMemoryAct() - actualAct.Load(swarm_ref) - actualEak, _ := actualAct.Lookup(lookupKey) - if !bytes.Equal(actualEak, encryptedAccesskey) { - t.Errorf("actualAct.Load() value is not the expected %s != %s", hex.EncodeToString(actualEak), hex.EncodeToString(encryptedAccesskey)) - } -} -*/ diff --git a/pkg/dynamicaccess/controller.go b/pkg/dynamicaccess/controller.go index b1c6e306934..d118e22489f 100644 --- a/pkg/dynamicaccess/controller.go +++ b/pkg/dynamicaccess/controller.go @@ -3,6 +3,7 @@ package dynamicaccess import ( "crypto/ecdsa" + kvsmock "github.com/ethersphere/bee/pkg/kvs/mock" "github.com/ethersphere/bee/pkg/swarm" ) @@ -18,29 +19,30 @@ type defaultController struct { } func (c *defaultController) DownloadHandler(timestamp int64, enryptedRef swarm.Address, publisher *ecdsa.PublicKey, tag string) (swarm.Address, error) { - _, err := c.history.Lookup(timestamp) + kvs, err := c.history.Lookup(timestamp) if err != nil { return swarm.EmptyAddress, err } - addr, err := c.accessLogic.DecryptRef(swarm.EmptyAddress, enryptedRef, publisher) + addr, err := c.accessLogic.DecryptRef(kvs, enryptedRef, publisher) return addr, err } func (c *defaultController) UploadHandler(ref swarm.Address, publisher *ecdsa.PublicKey, topic string) (swarm.Address, error) { - act, err := c.history.Lookup(0) + kvs, err := c.history.Lookup(0) if err != nil { return swarm.EmptyAddress, err } - var actRef swarm.Address - if act == nil { + if kvs == nil { // new feed - actRef, err = c.granteeManager.Publish(swarm.EmptyAddress, publisher, topic) + // TODO: putter session to create kvs + kvs = kvsmock.New() + _, err = c.granteeManager.Publish(kvs, publisher, topic) if err != nil { return swarm.EmptyAddress, err } } - //FIXME: check if ACT is consistent with the grantee list - return c.accessLogic.EncryptRef(actRef, publisher, ref) + //FIXME: check if kvs is consistent with the grantee list + return c.accessLogic.EncryptRef(kvs, publisher, ref) } func NewController(history History, granteeManager GranteeManager, accessLogic ActLogic) Controller { diff --git a/pkg/dynamicaccess/controller_test.go b/pkg/dynamicaccess/controller_test.go index 8f876232b05..63f80059e08 100644 --- a/pkg/dynamicaccess/controller_test.go +++ b/pkg/dynamicaccess/controller_test.go @@ -12,6 +12,7 @@ import ( "github.com/ethersphere/bee/pkg/dynamicaccess" "github.com/ethersphere/bee/pkg/dynamicaccess/mock" "github.com/ethersphere/bee/pkg/encryption" + kvsmock "github.com/ethersphere/bee/pkg/kvs/mock" "github.com/ethersphere/bee/pkg/swarm" "golang.org/x/crypto/sha3" ) @@ -22,10 +23,10 @@ func mockTestHistory(key, val []byte) dynamicaccess.History { var ( h = mock.NewHistory() now = time.Now() - act = dynamicaccess.NewInMemoryAct() + s = kvsmock.New() ) - act.Add(swarm.EmptyAddress, key, val) - h.Insert(now.AddDate(-3, 0, 0).Unix(), act) + _ = s.Put(key, val) + h.Insert(now.AddDate(-3, 0, 0).Unix(), s) return h } diff --git a/pkg/dynamicaccess/grantee_manager.go b/pkg/dynamicaccess/grantee_manager.go index 0627db9794a..004a933b006 100644 --- a/pkg/dynamicaccess/grantee_manager.go +++ b/pkg/dynamicaccess/grantee_manager.go @@ -3,13 +3,14 @@ package dynamicaccess import ( "crypto/ecdsa" + "github.com/ethersphere/bee/pkg/kvs" "github.com/ethersphere/bee/pkg/swarm" ) type GranteeManager interface { Get(topic string) []*ecdsa.PublicKey Add(topic string, addList []*ecdsa.PublicKey) error - Publish(rootHash swarm.Address, publisher *ecdsa.PublicKey, topic string) (swarm.Address, error) + Publish(kvs kvs.KeyValueStore, publisher *ecdsa.PublicKey, topic string) (swarm.Address, error) // HandleGrantees(topic string, addList, removeList []*ecdsa.PublicKey) *Act @@ -36,10 +37,10 @@ func (gm *granteeManager) Add(topic string, addList []*ecdsa.PublicKey) error { return gm.granteeList.Add(topic, addList) } -func (gm *granteeManager) Publish(rootHash swarm.Address, publisher *ecdsa.PublicKey, topic string) (swarm.Address, error) { - ref, err := gm.accessLogic.AddPublisher(rootHash, publisher) +func (gm *granteeManager) Publish(kvs kvs.KeyValueStore, publisher *ecdsa.PublicKey, topic string) (swarm.Address, error) { + err := gm.accessLogic.AddPublisher(kvs, publisher) for _, grantee := range gm.granteeList.Get(topic) { - ref, err = gm.accessLogic.AddGrantee(ref, publisher, grantee, nil) + err = gm.accessLogic.AddGrantee(kvs, publisher, grantee, nil) } - return ref, err + return swarm.EmptyAddress, err } diff --git a/pkg/dynamicaccess/grantee_manager_test.go b/pkg/dynamicaccess/grantee_manager_test.go index 2144fd1410a..7ac43425e10 100644 --- a/pkg/dynamicaccess/grantee_manager_test.go +++ b/pkg/dynamicaccess/grantee_manager_test.go @@ -8,13 +8,12 @@ import ( "testing" "github.com/ethersphere/bee/pkg/dynamicaccess" - "github.com/ethersphere/bee/pkg/swarm" + kvsmock "github.com/ethersphere/bee/pkg/kvs/mock" ) func setupAccessLogic(privateKey *ecdsa.PrivateKey) dynamicaccess.ActLogic { - act := dynamicaccess.NewInMemoryAct() si := dynamicaccess.NewDefaultSession(privateKey) - al := dynamicaccess.NewLogic(si, act) + al := dynamicaccess.NewLogic(si) return al } @@ -33,6 +32,7 @@ func TestAdd(t *testing.T) { if err != nil { t.Errorf("Add() returned an error") } - m.Publish(swarm.EmptyAddress, &pub.PublicKey, "topic") + s := kvsmock.New() + m.Publish(s, &pub.PublicKey, "topic") fmt.Println("") } diff --git a/pkg/dynamicaccess/history.go b/pkg/dynamicaccess/history.go index e2705fffeac..63ec00847a6 100644 --- a/pkg/dynamicaccess/history.go +++ b/pkg/dynamicaccess/history.go @@ -2,34 +2,35 @@ package dynamicaccess import ( "github.com/ethereum/go-ethereum/common" + "github.com/ethersphere/bee/pkg/kvs" ) type History interface { - Add(timestamp int64, act Act) error - Get(timestamp int64) (Act, error) - Lookup(at int64) (Act, error) + Add(timestamp int64, kvs kvs.KeyValueStore) error + Get(timestamp int64) (kvs.KeyValueStore, error) + Lookup(at int64) (kvs.KeyValueStore, error) } var _ History = (*history)(nil) type history struct { - history map[int64]*Act + history map[int64]*kvs.KeyValueStore } func NewHistory(topic []byte, owner common.Address) *history { - return &history{history: make(map[int64]*Act)} + return &history{history: make(map[int64]*kvs.KeyValueStore)} } -func (h *history) Add(timestamp int64, act Act) error { +func (h *history) Add(timestamp int64, kvs kvs.KeyValueStore) error { return nil } -func (h *history) Lookup(at int64) (Act, error) { +func (h *history) Lookup(at int64) (kvs.KeyValueStore, error) { return nil, nil } -func (h *history) Get(timestamp int64) (Act, error) { +func (h *history) Get(timestamp int64) (kvs.KeyValueStore, error) { // get the feed return nil, nil } diff --git a/pkg/dynamicaccess/history_test.go b/pkg/dynamicaccess/history_test.go index e501e075697..c2164df7d39 100644 --- a/pkg/dynamicaccess/history_test.go +++ b/pkg/dynamicaccess/history_test.go @@ -7,7 +7,7 @@ import ( "github.com/ethersphere/bee/pkg/dynamicaccess" "github.com/ethersphere/bee/pkg/dynamicaccess/mock" - "github.com/ethersphere/bee/pkg/swarm" + kvsmock "github.com/ethersphere/bee/pkg/kvs/mock" "github.com/stretchr/testify/assert" ) @@ -32,8 +32,8 @@ func TestHistoryLookup(t *testing.T) { for _, tt := range tests { t.Run("", func(t *testing.T) { - actAt, _ := h.Lookup(tt.input) - output, _ := actAt.Lookup(swarm.EmptyAddress, []byte("key1")) + sAt, _ := h.Lookup(tt.input) + output, _ := sAt.Get([]byte("key1")) assert.Equal(t, output, hex.EncodeToString([]byte(tt.expected))) }) } @@ -41,19 +41,19 @@ func TestHistoryLookup(t *testing.T) { func prepareTestHistory() dynamicaccess.History { var ( - h = mock.NewHistory() - now = time.Now() - act1 = dynamicaccess.NewInMemoryAct() - act2 = dynamicaccess.NewInMemoryAct() - act3 = dynamicaccess.NewInMemoryAct() + h = mock.NewHistory() + now = time.Now() + s1 = kvsmock.New() + s2 = kvsmock.New() + s3 = kvsmock.New() ) - act1.Add(swarm.EmptyAddress, []byte("key1"), []byte("value1")) - act2.Add(swarm.EmptyAddress, []byte("key1"), []byte("value2")) - act3.Add(swarm.EmptyAddress, []byte("key1"), []byte("value3")) + s1.Put([]byte("key1"), []byte("value1")) + s2.Put([]byte("key1"), []byte("value2")) + s3.Put([]byte("key1"), []byte("value3")) - h.Insert(now.AddDate(-3, 0, 0).Unix(), act1) - h.Insert(now.AddDate(-2, 0, 0).Unix(), act2) - h.Insert(now.AddDate(-1, 0, 0).Unix(), act3) + h.Insert(now.AddDate(-3, 0, 0).Unix(), s1) + h.Insert(now.AddDate(-2, 0, 0).Unix(), s2) + h.Insert(now.AddDate(-1, 0, 0).Unix(), s3) return h } diff --git a/pkg/dynamicaccess/mock/act.go b/pkg/dynamicaccess/mock/act.go deleted file mode 100644 index 721a4778829..00000000000 --- a/pkg/dynamicaccess/mock/act.go +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2024 The Swarm Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package mock - -import ( - "github.com/ethersphere/bee/pkg/dynamicaccess" - "github.com/ethersphere/bee/pkg/swarm" -) - -type ActMock struct { - AddFunc func(root swarm.Address, key []byte, val []byte) (swarm.Address, error) - LookupFunc func(root swarm.Address, key []byte) ([]byte, error) - LoadFunc func(addr swarm.Address) error - StoreFunc func() (swarm.Address, error) -} - -var _ dynamicaccess.Act = (*ActMock)(nil) - -func (act *ActMock) Add(root swarm.Address, key []byte, val []byte) (swarm.Address, error) { - if act.AddFunc == nil { - return swarm.EmptyAddress, nil - } - return act.AddFunc(root, key, val) -} - -func (act *ActMock) Lookup(root swarm.Address, key []byte) ([]byte, error) { - if act.LookupFunc == nil { - return make([]byte, 0), nil - } - return act.LookupFunc(root, key) -} - -func (act *ActMock) Load(addr swarm.Address) error { - if act.LoadFunc == nil { - return nil - } - return act.LoadFunc(addr) -} - -func (act *ActMock) Store() (swarm.Address, error) { - if act.StoreFunc == nil { - return swarm.EmptyAddress, nil - } - return act.StoreFunc() -} - -func NewActMock( - addFunc func(swarm.Address, []byte, []byte) (swarm.Address, error), - getFunc func(swarm.Address, []byte) ([]byte, error)) dynamicaccess.Act { - return &ActMock{ - AddFunc: addFunc, - LookupFunc: getFunc, - } -} diff --git a/pkg/dynamicaccess/mock/history.go b/pkg/dynamicaccess/mock/history.go index 68559c2277a..91b544a0760 100644 --- a/pkg/dynamicaccess/mock/history.go +++ b/pkg/dynamicaccess/mock/history.go @@ -6,31 +6,31 @@ import ( "time" "github.com/ethersphere/bee/pkg/crypto" - "github.com/ethersphere/bee/pkg/dynamicaccess" "github.com/ethersphere/bee/pkg/feeds" + "github.com/ethersphere/bee/pkg/kvs" "github.com/ethersphere/bee/pkg/storage" "github.com/ethersphere/bee/pkg/swarm" ) type historyMock struct { - history map[int64]dynamicaccess.Act + history map[int64]kvs.KeyValueStore } func NewHistory() *historyMock { - return &historyMock{history: make(map[int64]dynamicaccess.Act)} + return &historyMock{history: make(map[int64]kvs.KeyValueStore)} } -func (h *historyMock) Add(timestamp int64, act dynamicaccess.Act) error { +func (h *historyMock) Add(timestamp int64, act kvs.KeyValueStore) error { h.history[timestamp] = act return nil } -func (h *historyMock) Insert(timestamp int64, act dynamicaccess.Act) *historyMock { +func (h *historyMock) Insert(timestamp int64, act kvs.KeyValueStore) *historyMock { h.Add(timestamp, act) return h } -func (h *historyMock) Lookup(at int64) (dynamicaccess.Act, error) { +func (h *historyMock) Lookup(at int64) (kvs.KeyValueStore, error) { keys := []int64{} for k := range h.history { keys = append(keys, k) @@ -52,7 +52,7 @@ func (h *historyMock) Lookup(at int64) (dynamicaccess.Act, error) { return nil, nil } -func (h *historyMock) Get(timestamp int64) (dynamicaccess.Act, error) { +func (h *historyMock) Get(timestamp int64) (kvs.KeyValueStore, error) { return h.history[timestamp], nil } diff --git a/pkg/kvs/kvs.go b/pkg/kvs/kvs.go index 65c2a0be8b9..91731f24af3 100644 --- a/pkg/kvs/kvs.go +++ b/pkg/kvs/kvs.go @@ -1,34 +1,70 @@ package kvs import ( - "errors" + "context" + "encoding/hex" - "github.com/ethersphere/bee/pkg/api" - "github.com/ethersphere/bee/pkg/kvs/manifest" - "github.com/ethersphere/bee/pkg/kvs/memory" + "github.com/ethersphere/bee/pkg/file" + "github.com/ethersphere/bee/pkg/manifest" + "github.com/ethersphere/bee/pkg/storer" "github.com/ethersphere/bee/pkg/swarm" ) -var ErrInvalidKvsType = errors.New("kvs: invalid type") - type KeyValueStore interface { - Get(rootHash swarm.Address, key []byte) ([]byte, error) - Put(rootHash swarm.Address, key, value []byte) (swarm.Address, error) + Get(key []byte) ([]byte, error) + Put(key, value []byte) error + Save() (swarm.Address, error) +} + +type keyValueStore struct { + manifest manifest.Interface + putter storer.PutterSession +} + +var _ KeyValueStore = (*keyValueStore)(nil) + +// TODO: pass context as dep. +func (s *keyValueStore) Get(key []byte) ([]byte, error) { + entry, err := s.manifest.Lookup(context.Background(), hex.EncodeToString(key)) + if err != nil { + return nil, err + } + ref := entry.Reference() + return ref.Bytes(), nil +} + +func (s *keyValueStore) Put(key []byte, value []byte) error { + return s.manifest.Add(context.Background(), hex.EncodeToString(key), manifest.NewEntry(swarm.NewAddress(value), map[string]string{})) +} + +func (s *keyValueStore) Save() (swarm.Address, error) { + ref, err := s.manifest.Store(context.Background()) + if err != nil { + return swarm.ZeroAddress, err + } + err = s.putter.Done(ref) + if err != nil { + return swarm.ZeroAddress, err + } + return ref, nil } -// func NewDefaultKeyValueStore(storer api.Storer) (KeyValueStore, error) { -// return NewKeyValueStore(storer, memory.KvsTypeMemory) -// } - -func NewKeyValueStore(storer api.Storer, kvsType string) (KeyValueStore, error) { - switch kvsType { - case "": - return memory.NewMemoryKeyValueStore() - case memory.KvsTypeMemory: - return memory.NewMemoryKeyValueStore() - case manifest.KvsTypeManifest: - return manifest.NewManifestKeyValueStore(storer) - default: - return nil, ErrInvalidKvsType +func New(ls file.LoadSaver, putter storer.PutterSession, rootHash swarm.Address) KeyValueStore { + var ( + manif manifest.Interface + err error + ) + if swarm.ZeroAddress.Equal(rootHash) || swarm.EmptyAddress.Equal(rootHash) { + manif, err = manifest.NewSimpleManifest(ls) + } else { + manif, err = manifest.NewSimpleManifestReference(rootHash, ls) + } + if err != nil { + return nil + } + + return &keyValueStore{ + manifest: manif, + putter: putter, } } diff --git a/pkg/kvs/kvs_test.go b/pkg/kvs/kvs_test.go new file mode 100644 index 00000000000..234802de2e3 --- /dev/null +++ b/pkg/kvs/kvs_test.go @@ -0,0 +1,132 @@ +// Copyright 2024 The Swarm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package kvs_test + +import ( + "bytes" + "context" + "encoding/hex" + "testing" + + "github.com/ethersphere/bee/pkg/file" + "github.com/ethersphere/bee/pkg/file/loadsave" + "github.com/ethersphere/bee/pkg/file/pipeline" + "github.com/ethersphere/bee/pkg/file/pipeline/builder" + "github.com/ethersphere/bee/pkg/file/redundancy" + "github.com/ethersphere/bee/pkg/kvs" + "github.com/ethersphere/bee/pkg/storage" + mockstorer "github.com/ethersphere/bee/pkg/storer/mock" + "github.com/ethersphere/bee/pkg/swarm" +) + +var mockStorer = mockstorer.New() + +func requestPipelineFactory(ctx context.Context, s storage.Putter, encrypt bool, rLevel redundancy.Level) func() pipeline.Interface { + return func() pipeline.Interface { + return builder.NewPipelineBuilder(ctx, s, encrypt, rLevel) + } +} + +func createLs() file.LoadSaver { + return loadsave.New(mockStorer.ChunkStore(), mockStorer.Cache(), requestPipelineFactory(context.Background(), mockStorer.Cache(), false, redundancy.NONE)) +} + +func keyValuePair(t *testing.T) ([]byte, []byte) { + return swarm.RandAddress(t).Bytes(), swarm.RandAddress(t).Bytes() +} + +func TestKvsAddLookup(t *testing.T) { + ls := createLs() + + putter := mockStorer.DirectUpload() + s := kvs.New(ls, putter, swarm.ZeroAddress) + + lookupKey, encryptedAccesskey := keyValuePair(t) + + err := s.Put(lookupKey, encryptedAccesskey) + if err != nil { + t.Errorf("Add() should not return an error: %v", err) + } + + key, err := s.Get(lookupKey) + if err != nil { + t.Errorf("Lookup() should not return an error: %v", err) + } + + if !bytes.Equal(key, encryptedAccesskey) { + t.Errorf("Get() value is not the expected %s != %s", hex.EncodeToString(key), hex.EncodeToString(encryptedAccesskey)) + } + +} + +func TestKvsAddLookupWithSave(t *testing.T) { + ls := createLs() + putter := mockStorer.DirectUpload() + s1 := kvs.New(ls, putter, swarm.ZeroAddress) + lookupKey, encryptedAccesskey := keyValuePair(t) + + err := s1.Put(lookupKey, encryptedAccesskey) + if err != nil { + t.Fatalf("Add() should not return an error: %v", err) + } + ref, err := s1.Save() + if err != nil { + t.Fatalf("Save() should not return an error: %v", err) + } + s2 := kvs.New(ls, putter, ref) + key, err := s2.Get(lookupKey) + if err != nil { + t.Fatalf("Lookup() should not return an error: %v", err) + } + + if !bytes.Equal(key, encryptedAccesskey) { + t.Errorf("Get() value is not the expected %s != %s", hex.EncodeToString(key), hex.EncodeToString(encryptedAccesskey)) + } + +} + +func TestKvsAddSaveAdd(t *testing.T) { + ls := createLs() + putter := mockStorer.DirectUpload() + kvs1 := kvs.New(ls, putter, swarm.ZeroAddress) + kvs1key1, kvs1val1 := keyValuePair(t) + + err := kvs1.Put(kvs1key1, kvs1val1) + if err != nil { + t.Fatalf("Add() should not return an error: %v", err) + } + ref, err := kvs1.Save() + if err != nil { + t.Fatalf("Save() should not return an error: %v", err) + } + + // New KVS + kvs2 := kvs.New(ls, putter, ref) + + kvs2key1, kvs2val1 := keyValuePair(t) + + // put after save + kvs2.Put(kvs2key1, kvs2val1) + + // get after save + kvs2get1, err := kvs2.Get(kvs2key1) + if err != nil { + t.Fatalf("Lookup() should not return an error: %v", err) + } + if !bytes.Equal(kvs2get1, kvs2val1) { + t.Errorf("Get() value is not the expected %s != %s", hex.EncodeToString(kvs2key1), hex.EncodeToString(kvs2val1)) + } + + // get before Save + kvs2get2, err := kvs2.Get(kvs1key1) + + if err != nil { + t.Fatalf("Lookup() should not return an error: %v", err) + } + if !bytes.Equal(kvs2get2, kvs1val1) { + t.Errorf("Get() value is not the expected %s != %s", hex.EncodeToString(kvs2get2), hex.EncodeToString(kvs1val1)) + } + +} diff --git a/pkg/kvs/manifest/kvs.go b/pkg/kvs/manifest/kvs.go deleted file mode 100644 index 19ca983d54a..00000000000 --- a/pkg/kvs/manifest/kvs.go +++ /dev/null @@ -1,86 +0,0 @@ -package manifest - -import ( - "context" - "encoding/hex" - - "github.com/ethersphere/bee/pkg/api" - "github.com/ethersphere/bee/pkg/file/loadsave" - "github.com/ethersphere/bee/pkg/file/pipeline" - "github.com/ethersphere/bee/pkg/file/pipeline/builder" - "github.com/ethersphere/bee/pkg/file/redundancy" - "github.com/ethersphere/bee/pkg/manifest" - "github.com/ethersphere/bee/pkg/storage" - "github.com/ethersphere/bee/pkg/swarm" -) - -const ( - // KvsTypeManifest represents - KvsTypeManifest = "Manifest" -) - -type ManifestKeyValueStore interface { - Get(rootHash swarm.Address, key []byte) ([]byte, error) - Put(rootHash swarm.Address, key, value []byte) (swarm.Address, error) -} - -type manifestKeyValueStore struct { - storer api.Storer -} - -// TODO: pass context as dep. -func (m *manifestKeyValueStore) Get(rootHash swarm.Address, key []byte) ([]byte, error) { - ls := loadsave.NewReadonly(m.storer.ChunkStore()) - // existing manif - manif, err := manifest.NewSimpleManifestReference(rootHash, ls) - if err != nil { - return nil, err - } - entry, err := manif.Lookup(context.Background(), hex.EncodeToString(key)) - if err != nil { - return nil, err - } - ref := entry.Reference() - return ref.Bytes(), nil -} - -func (m *manifestKeyValueStore) Put(rootHash swarm.Address, key []byte, value []byte) (swarm.Address, error) { - factory := requestPipelineFactory(context.Background(), m.storer.Cache(), false, redundancy.NONE) - ls := loadsave.New(m.storer.ChunkStore(), m.storer.Cache(), factory) - // existing manif - manif, err := manifest.NewSimpleManifestReference(rootHash, ls) - if err != nil { - // new manif - manif, err = manifest.NewSimpleManifest(ls) - if err != nil { - return swarm.EmptyAddress, err - } - } - err = manif.Add(context.Background(), hex.EncodeToString(key), manifest.NewEntry(swarm.NewAddress(value), map[string]string{})) - if err != nil { - return swarm.EmptyAddress, err - } - manifRef, err := manif.Store(context.Background()) - if err != nil { - return swarm.EmptyAddress, err - } - - putter := m.storer.DirectUpload() - err = putter.Done(manifRef) - if err != nil { - return swarm.EmptyAddress, err - } - return manifRef, nil -} - -func NewManifestKeyValueStore(storer api.Storer) (ManifestKeyValueStore, error) { - return &manifestKeyValueStore{ - storer: storer, - }, nil -} - -func requestPipelineFactory(ctx context.Context, s storage.Putter, encrypt bool, rLevel redundancy.Level) func() pipeline.Interface { - return func() pipeline.Interface { - return builder.NewPipelineBuilder(ctx, s, encrypt, rLevel) - } -} diff --git a/pkg/kvs/memory/kvs.go b/pkg/kvs/mock/kvs.go similarity index 55% rename from pkg/kvs/memory/kvs.go rename to pkg/kvs/mock/kvs.go index 3a1abc69c0e..1c2866137df 100644 --- a/pkg/kvs/memory/kvs.go +++ b/pkg/kvs/mock/kvs.go @@ -1,22 +1,13 @@ -package memory +package mock import ( "encoding/hex" "sync" + "github.com/ethersphere/bee/pkg/kvs" "github.com/ethersphere/bee/pkg/swarm" ) -const ( - // KvsTypeMemory represents - KvsTypeMemory = "Memory" -) - -type MemoryKeyValueStore interface { - Get(rootHash swarm.Address, key []byte) ([]byte, error) - Put(rootHash swarm.Address, key, value []byte) (swarm.Address, error) -} - var lock = &sync.Mutex{} type single struct { @@ -47,21 +38,27 @@ func getMemory() map[string][]byte { return mem.memoryMock } -type memoryKeyValueStore struct { +type mockKeyValueStore struct { } -func (m *memoryKeyValueStore) Get(rootHash swarm.Address, key []byte) ([]byte, error) { +var _ kvs.KeyValueStore = (*mockKeyValueStore)(nil) + +func (m *mockKeyValueStore) Get(key []byte) ([]byte, error) { mem := getMemory() val := mem[hex.EncodeToString(key)] return val, nil } -func (m *memoryKeyValueStore) Put(rootHash swarm.Address, key []byte, value []byte) (swarm.Address, error) { +func (m *mockKeyValueStore) Put(key []byte, value []byte) error { mem := getMemory() mem[hex.EncodeToString(key)] = value - return swarm.EmptyAddress, nil + return nil +} + +func (s *mockKeyValueStore) Save() (swarm.Address, error) { + return swarm.ZeroAddress, nil } -func NewMemoryKeyValueStore() (MemoryKeyValueStore, error) { - return &memoryKeyValueStore{}, nil +func New() kvs.KeyValueStore { + return &mockKeyValueStore{} } From 98a2d5fdf22760a747d614dd10fe1e3dd775c209 Mon Sep 17 00:00:00 2001 From: Kexort Date: Thu, 28 Mar 2024 10:58:49 +0100 Subject: [PATCH 17/34] Add referenced mock kvs (#26) --- pkg/kvs/mock/kvs.go | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/pkg/kvs/mock/kvs.go b/pkg/kvs/mock/kvs.go index 1c2866137df..fc091a88417 100644 --- a/pkg/kvs/mock/kvs.go +++ b/pkg/kvs/mock/kvs.go @@ -12,7 +12,7 @@ var lock = &sync.Mutex{} type single struct { // TODO string -> []byte ? - memoryMock map[string][]byte + memoryMock map[string]map[string][]byte } var singleInMemorySwarm *single @@ -23,13 +23,13 @@ func getInMemorySwarm() *single { defer lock.Unlock() if singleInMemorySwarm == nil { singleInMemorySwarm = &single{ - memoryMock: make(map[string][]byte)} + memoryMock: make(map[string]map[string][]byte)} } } return singleInMemorySwarm } -func getMemory() map[string][]byte { +func getMemory() map[string]map[string][]byte { ch := make(chan *single) go func() { ch <- getInMemorySwarm() @@ -39,26 +39,34 @@ func getMemory() map[string][]byte { } type mockKeyValueStore struct { + address swarm.Address } var _ kvs.KeyValueStore = (*mockKeyValueStore)(nil) func (m *mockKeyValueStore) Get(key []byte) ([]byte, error) { mem := getMemory() - val := mem[hex.EncodeToString(key)] + val := mem[m.address.String()][hex.EncodeToString(key)] return val, nil } func (m *mockKeyValueStore) Put(key []byte, value []byte) error { mem := getMemory() - mem[hex.EncodeToString(key)] = value + if _, ok := mem[m.address.String()]; !ok { + mem[m.address.String()] = make(map[string][]byte) + } + mem[m.address.String()][hex.EncodeToString(key)] = value return nil } -func (s *mockKeyValueStore) Save() (swarm.Address, error) { - return swarm.ZeroAddress, nil +func (m *mockKeyValueStore) Save() (swarm.Address, error) { + return m.address, nil } func New() kvs.KeyValueStore { - return &mockKeyValueStore{} + return &mockKeyValueStore{address: swarm.EmptyAddress} +} + +func NewReference(address swarm.Address) kvs.KeyValueStore { + return &mockKeyValueStore{address: address} } From a549d8482ef5e57287213d16ce1fc304a59c3471 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferenc=20S=C3=A1rai?= Date: Thu, 28 Mar 2024 14:10:17 +0100 Subject: [PATCH 18/34] Act kvs test (#27) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * (test:) Refactor tests * (fix:) Save reset counter --------- Co-authored-by: Ferenc Sárai --- pkg/kvs/kvs.go | 17 +++- pkg/kvs/kvs_test.go | 221 ++++++++++++++++++++++++++------------------ 2 files changed, 145 insertions(+), 93 deletions(-) diff --git a/pkg/kvs/kvs.go b/pkg/kvs/kvs.go index 91731f24af3..1106f2ceee8 100644 --- a/pkg/kvs/kvs.go +++ b/pkg/kvs/kvs.go @@ -1,8 +1,13 @@ +// Copyright 2024 The Swarm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package kvs import ( "context" "encoding/hex" + "errors" "github.com/ethersphere/bee/pkg/file" "github.com/ethersphere/bee/pkg/manifest" @@ -19,6 +24,7 @@ type KeyValueStore interface { type keyValueStore struct { manifest manifest.Interface putter storer.PutterSession + putCnt int } var _ KeyValueStore = (*keyValueStore)(nil) @@ -34,10 +40,18 @@ func (s *keyValueStore) Get(key []byte) ([]byte, error) { } func (s *keyValueStore) Put(key []byte, value []byte) error { - return s.manifest.Add(context.Background(), hex.EncodeToString(key), manifest.NewEntry(swarm.NewAddress(value), map[string]string{})) + err := s.manifest.Add(context.Background(), hex.EncodeToString(key), manifest.NewEntry(swarm.NewAddress(value), map[string]string{})) + if err != nil { + return err + } + s.putCnt++ + return nil } func (s *keyValueStore) Save() (swarm.Address, error) { + if s.putCnt == 0 { + return swarm.ZeroAddress, errors.New("nothing to save") + } ref, err := s.manifest.Store(context.Background()) if err != nil { return swarm.ZeroAddress, err @@ -46,6 +60,7 @@ func (s *keyValueStore) Save() (swarm.Address, error) { if err != nil { return swarm.ZeroAddress, err } + s.putCnt = 0 return ref, nil } diff --git a/pkg/kvs/kvs_test.go b/pkg/kvs/kvs_test.go index 234802de2e3..309250dacff 100644 --- a/pkg/kvs/kvs_test.go +++ b/pkg/kvs/kvs_test.go @@ -5,9 +5,7 @@ package kvs_test import ( - "bytes" "context" - "encoding/hex" "testing" "github.com/ethersphere/bee/pkg/file" @@ -19,6 +17,7 @@ import ( "github.com/ethersphere/bee/pkg/storage" mockstorer "github.com/ethersphere/bee/pkg/storer/mock" "github.com/ethersphere/bee/pkg/swarm" + "github.com/stretchr/testify/assert" ) var mockStorer = mockstorer.New() @@ -37,96 +36,134 @@ func keyValuePair(t *testing.T) ([]byte, []byte) { return swarm.RandAddress(t).Bytes(), swarm.RandAddress(t).Bytes() } -func TestKvsAddLookup(t *testing.T) { - ls := createLs() - - putter := mockStorer.DirectUpload() - s := kvs.New(ls, putter, swarm.ZeroAddress) - - lookupKey, encryptedAccesskey := keyValuePair(t) - - err := s.Put(lookupKey, encryptedAccesskey) - if err != nil { - t.Errorf("Add() should not return an error: %v", err) - } - - key, err := s.Get(lookupKey) - if err != nil { - t.Errorf("Lookup() should not return an error: %v", err) - } - - if !bytes.Equal(key, encryptedAccesskey) { - t.Errorf("Get() value is not the expected %s != %s", hex.EncodeToString(key), hex.EncodeToString(encryptedAccesskey)) - } - -} - -func TestKvsAddLookupWithSave(t *testing.T) { - ls := createLs() - putter := mockStorer.DirectUpload() - s1 := kvs.New(ls, putter, swarm.ZeroAddress) - lookupKey, encryptedAccesskey := keyValuePair(t) - - err := s1.Put(lookupKey, encryptedAccesskey) - if err != nil { - t.Fatalf("Add() should not return an error: %v", err) - } - ref, err := s1.Save() - if err != nil { - t.Fatalf("Save() should not return an error: %v", err) - } - s2 := kvs.New(ls, putter, ref) - key, err := s2.Get(lookupKey) - if err != nil { - t.Fatalf("Lookup() should not return an error: %v", err) - } - - if !bytes.Equal(key, encryptedAccesskey) { - t.Errorf("Get() value is not the expected %s != %s", hex.EncodeToString(key), hex.EncodeToString(encryptedAccesskey)) - } - +func TestKvs(t *testing.T) { + + s := kvs.New(createLs(), mockStorer.DirectUpload(), swarm.ZeroAddress) + key, val := keyValuePair(t) + + t.Run("Get non-existent key should return error", func(t *testing.T) { + _, err := s.Get([]byte{1}) + assert.Error(t, err) + }) + + t.Run("Multiple Get with same key, no error", func(t *testing.T) { + err := s.Put(key, val) + assert.NoError(t, err) + + // get #1 + v, err := s.Get(key) + assert.NoError(t, err) + assert.Equal(t, val, v) + // get #2 + v, err = s.Get(key) + assert.NoError(t, err) + assert.Equal(t, val, v) + }) + + t.Run("Get should return value equal to put value", func(t *testing.T) { + var ( + key1 []byte = []byte{1} + key2 []byte = []byte{2} + key3 []byte = []byte{3} + ) + testCases := []struct { + name string + key []byte + val []byte + }{ + { + name: "Test key = 1", + key: key1, + val: []byte{11}, + }, + { + name: "Test key = 2", + key: key2, + val: []byte{22}, + }, + { + name: "Test overwrite key = 1", + key: key1, + val: []byte{111}, + }, + { + name: "Test key = 3", + key: key3, + val: []byte{33}, + }, + { + name: "Test key = 3 with same value", + key: key3, + val: []byte{33}, + }, + { + name: "Test key = 3 with value for key1", + key: key3, + val: []byte{11}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := s.Put(tc.key, tc.val) + assert.NoError(t, err) + retVal, err := s.Get(tc.key) + assert.NoError(t, err) + assert.Equal(t, tc.val, retVal) + }) + } + }) } -func TestKvsAddSaveAdd(t *testing.T) { - ls := createLs() - putter := mockStorer.DirectUpload() - kvs1 := kvs.New(ls, putter, swarm.ZeroAddress) - kvs1key1, kvs1val1 := keyValuePair(t) - - err := kvs1.Put(kvs1key1, kvs1val1) - if err != nil { - t.Fatalf("Add() should not return an error: %v", err) - } - ref, err := kvs1.Save() - if err != nil { - t.Fatalf("Save() should not return an error: %v", err) - } - - // New KVS - kvs2 := kvs.New(ls, putter, ref) - - kvs2key1, kvs2val1 := keyValuePair(t) - - // put after save - kvs2.Put(kvs2key1, kvs2val1) - - // get after save - kvs2get1, err := kvs2.Get(kvs2key1) - if err != nil { - t.Fatalf("Lookup() should not return an error: %v", err) - } - if !bytes.Equal(kvs2get1, kvs2val1) { - t.Errorf("Get() value is not the expected %s != %s", hex.EncodeToString(kvs2key1), hex.EncodeToString(kvs2val1)) - } - - // get before Save - kvs2get2, err := kvs2.Get(kvs1key1) - - if err != nil { - t.Fatalf("Lookup() should not return an error: %v", err) - } - if !bytes.Equal(kvs2get2, kvs1val1) { - t.Errorf("Get() value is not the expected %s != %s", hex.EncodeToString(kvs2get2), hex.EncodeToString(kvs1val1)) - } - +func TestKvs_Save(t *testing.T) { + key1, val1 := keyValuePair(t) + key2, val2 := keyValuePair(t) + t.Run("Save empty KVS return error", func(t *testing.T) { + s := kvs.New(createLs(), mockStorer.DirectUpload(), swarm.ZeroAddress) + _, err := s.Save() + assert.Error(t, err) + }) + t.Run("Save not empty KVS return valid swarm address", func(t *testing.T) { + s := kvs.New(createLs(), mockStorer.DirectUpload(), swarm.ZeroAddress) + s.Put(key1, val1) + ref, err := s.Save() + assert.NoError(t, err) + assert.True(t, ref.IsValidNonEmpty()) + }) + t.Run("Save KVS with one item, no error, pre-save value exist", func(t *testing.T) { + ls := createLs() + putter := mockStorer.DirectUpload() + s1 := kvs.New(ls, putter, swarm.ZeroAddress) + + err := s1.Put(key1, val1) + assert.NoError(t, err) + + ref, err := s1.Save() + assert.NoError(t, err) + + s2 := kvs.New(ls, putter, ref) + val, err := s2.Get(key1) + assert.NoError(t, err) + assert.Equal(t, val1, val) + }) + t.Run("Save KVS and add one item, no error, after-save value exist", func(t *testing.T) { + ls := createLs() + putter := mockStorer.DirectUpload() + + kvs1 := kvs.New(ls, putter, swarm.ZeroAddress) + + err := kvs1.Put(key1, val1) + assert.NoError(t, err) + ref, err := kvs1.Save() + assert.NoError(t, err) + + // New KVS + kvs2 := kvs.New(ls, putter, ref) + err = kvs2.Put(key2, val2) + assert.NoError(t, err) + + val, err := kvs2.Get(key2) + assert.NoError(t, err) + assert.Equal(t, val2, val) + }) } From 422cb2079600a1c207bb18d03142fc5be912bb52 Mon Sep 17 00:00:00 2001 From: Kexort Date: Tue, 2 Apr 2024 11:36:26 +0200 Subject: [PATCH 19/34] Small refactor + al test (#28) Adds TestDecryptRefWithGrantee_Success and replaces generateFixPrivateKey with getPrivKey Co-authored-by: Peter Ott --- pkg/dynamicaccess/accesslogic.go | 14 ++-- pkg/dynamicaccess/accesslogic_test.go | 107 +++++++++++++++++++------- pkg/dynamicaccess/publish.go | 12 --- pkg/dynamicaccess/publish_test.go | 11 --- pkg/dynamicaccess/timestamp.go | 10 --- pkg/dynamicaccess/timestamp_test.go | 1 - 6 files changed, 87 insertions(+), 68 deletions(-) delete mode 100644 pkg/dynamicaccess/publish.go delete mode 100644 pkg/dynamicaccess/publish_test.go delete mode 100644 pkg/dynamicaccess/timestamp.go delete mode 100644 pkg/dynamicaccess/timestamp_test.go diff --git a/pkg/dynamicaccess/accesslogic.go b/pkg/dynamicaccess/accesslogic.go index ad33ba5eb16..6c632a58351 100644 --- a/pkg/dynamicaccess/accesslogic.go +++ b/pkg/dynamicaccess/accesslogic.go @@ -14,7 +14,7 @@ var hashFunc = sha3.NewLegacyKeccak256 // Read-only interface for the ACT type Decryptor interface { // DecryptRef will return a decrypted reference, for given encrypted reference and grantee - DecryptRef(storage kvs.KeyValueStore, encryped_ref swarm.Address, publisher *ecdsa.PublicKey) (swarm.Address, error) + DecryptRef(storage kvs.KeyValueStore, encryptedRef swarm.Address, publisher *ecdsa.PublicKey) (swarm.Address, error) // Embedding the Session interface Session } @@ -26,14 +26,14 @@ type Control interface { // Adds a new grantee to the ACT AddGrantee(storage kvs.KeyValueStore, publisherPubKey, granteePubKey *ecdsa.PublicKey, accessKey *encryption.Key) error // Encrypts a Swarm reference for a given grantee - EncryptRef(storage kvs.KeyValueStore, grantee *ecdsa.PublicKey, ref swarm.Address) error + EncryptRef(storage kvs.KeyValueStore, grantee *ecdsa.PublicKey, ref swarm.Address) (swarm.Address, error) } type ActLogic struct { Session } -var _ Decryptor = (*ActLogic)(nil) +var _ Control = (*ActLogic)(nil) // Adds a new publisher to an empty act func (al ActLogic) AddPublisher(storage kvs.KeyValueStore, publisher *ecdsa.PublicKey) error { @@ -116,9 +116,9 @@ func (al *ActLogic) getKeys(publicKey *ecdsa.PublicKey) ([][]byte, error) { return al.Session.Key(publicKey, [][]byte{zeroByteArray, oneByteArray}) } -// DecryptRef will return a decrypted reference, for given encrypted reference and grantee -func (al ActLogic) DecryptRef(storage kvs.KeyValueStore, encryped_ref swarm.Address, grantee *ecdsa.PublicKey) (swarm.Address, error) { - keys, err := al.getKeys(grantee) +// DecryptRef will return a decrypted reference, for given encrypted reference and publisher +func (al ActLogic) DecryptRef(storage kvs.KeyValueStore, encryptedRef swarm.Address, publisher *ecdsa.PublicKey) (swarm.Address, error) { + keys, err := al.getKeys(publisher) if err != nil { return swarm.EmptyAddress, err } @@ -140,7 +140,7 @@ func (al ActLogic) DecryptRef(storage kvs.KeyValueStore, encryped_ref swarm.Addr // Decrypt reference refCipher := encryption.New(accessKey, 0, uint32(0), hashFunc) - ref, err := refCipher.Decrypt(encryped_ref.Bytes()) + ref, err := refCipher.Decrypt(encryptedRef.Bytes()) if err != nil { return swarm.EmptyAddress, err } diff --git a/pkg/dynamicaccess/accesslogic_test.go b/pkg/dynamicaccess/accesslogic_test.go index cb5ff0a6c78..8c2f2a6489f 100644 --- a/pkg/dynamicaccess/accesslogic_test.go +++ b/pkg/dynamicaccess/accesslogic_test.go @@ -3,10 +3,11 @@ package dynamicaccess_test import ( "crypto/ecdsa" "crypto/elliptic" + "crypto/rand" "encoding/hex" - "math/big" "testing" + "github.com/ethersphere/bee/pkg/crypto" "github.com/ethersphere/bee/pkg/dynamicaccess" kvsmock "github.com/ethersphere/bee/pkg/kvs/mock" "github.com/ethersphere/bee/pkg/swarm" @@ -14,33 +15,42 @@ import ( // Generates a new test environment with a fix private key func setupAccessLogic2() dynamicaccess.ActLogic { - privateKey := generateFixPrivateKey(1000) - diffieHellman := dynamicaccess.NewDefaultSession(&privateKey) + privateKey := getPrivKey(1) + diffieHellman := dynamicaccess.NewDefaultSession(privateKey) al := dynamicaccess.NewLogic(diffieHellman) return al } -// Generates a fixed identity with private and public key. The private key is generated from the input -func generateFixPrivateKey(input int64) ecdsa.PrivateKey { - fixedD := big.NewInt(input) - curve := elliptic.P256() - x, y := curve.ScalarBaseMult(fixedD.Bytes()) +func getPrivKey(keyNumber int) *ecdsa.PrivateKey { + var keyHex string - privateKey := ecdsa.PrivateKey{ - PublicKey: ecdsa.PublicKey{ - Curve: curve, - X: x, - Y: y, - }, - D: fixedD, + switch keyNumber { + case 0: + keyHex = "a786dd84b61485de12146fd9c4c02d87e8fd95f0542765cb7fc3d2e428c0bcfa" + case 1: + keyHex = "b786dd84b61485de12146fd9c4c02d87e8fd95f0542765cb7fc3d2e428c0bcfb" + case 2: + keyHex = "c786dd84b61485de12146fd9c4c02d87e8fd95f0542765cb7fc3d2e428c0bcfc" + default: + panic("Invalid key number") } - return privateKey + data, err := hex.DecodeString(keyHex) + if err != nil { + panic(err) + } + + privKey, err := crypto.DecodeSecp256k1PrivateKey(data) + if err != nil { + panic(err) + } + + return privKey } func TestDecryptRef_Success(t *testing.T) { - id0 := generateFixPrivateKey(0) + id0 := getPrivKey(0) s := kvsmock.New() al := setupAccessLogic2() err := al.AddPublisher(s, &id0.PublicKey) @@ -72,8 +82,51 @@ func TestDecryptRef_Success(t *testing.T) { } } +func TestDecryptRefWithGrantee_Success(t *testing.T) { + id0, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + diffieHellman := dynamicaccess.NewDefaultSession(id0) + al := dynamicaccess.NewLogic(diffieHellman) + + s := kvsmock.New() + err := al.AddPublisher(s, &id0.PublicKey) + if err != nil { + t.Errorf("AddPublisher: expected no error, got %v", err) + } + + id1, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + err = al.AddGrantee(s, &id0.PublicKey, &id1.PublicKey, nil) + if err != nil { + t.Errorf("AddNewGrantee: expected no error, got %v", err) + } + + byteRef, _ := hex.DecodeString("39a5ea87b141fe44aa609c3327ecd896c0e2122897f5f4bbacf74db1033c5559") + + expectedRef := swarm.NewAddress(byteRef) + t.Logf("encryptedRef: %s", expectedRef.String()) + + encryptedRef, err := al.EncryptRef(s, &id0.PublicKey, expectedRef) + t.Logf("encryptedRef: %s", encryptedRef.String()) + if err != nil { + t.Errorf("There was an error while calling EncryptRef: ") + t.Error(err) + } + + diffieHellman2 := dynamicaccess.NewDefaultSession(id1) + granteeAccessLogic := dynamicaccess.NewLogic(diffieHellman2) + acutalRef, err := granteeAccessLogic.DecryptRef(s, encryptedRef, &id0.PublicKey) + if err != nil { + t.Errorf("There was an error while calling Get: ") + t.Error(err) + } + + if expectedRef.Compare(acutalRef) != 0 { + + t.Errorf("Get gave back wrong Swarm reference!") + } +} + func TestDecryptRef_Error(t *testing.T) { - id0 := generateFixPrivateKey(0) + id0 := getPrivKey(0) s := kvsmock.New() al := setupAccessLogic2() @@ -89,13 +142,13 @@ func TestDecryptRef_Error(t *testing.T) { r, err := al.DecryptRef(s, encryptedRef, nil) if err == nil { t.Logf("r: %s", r.String()) - t.Errorf("Get should give back encrypted access key not found error!") + t.Errorf("Get should return encrypted access key not found error!") } } func TestAddPublisher(t *testing.T) { - id0 := generateFixPrivateKey(0) - savedLookupKey := "bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a" + id0 := getPrivKey(0) + savedLookupKey := "b6ee086390c280eeb9824c331a4427596f0c8510d5564bc1b6168d0059a46e2b" s := kvsmock.New() al := setupAccessLogic2() @@ -127,13 +180,13 @@ func TestAddPublisher(t *testing.T) { func TestAddNewGranteeToContent(t *testing.T) { - id0 := generateFixPrivateKey(0) - id1 := generateFixPrivateKey(1) - id2 := generateFixPrivateKey(2) + id0 := getPrivKey(0) + id1 := getPrivKey(1) + id2 := getPrivKey(2) - publisherLookupKey := "bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a" - firstAddedGranteeLookupKey := "e221a2abf64357260e8f2c937ee938aed98dce097e537c1a3fd4caf73510dbe4" - secondAddedGranteeLookupKey := "8fe8dff7cd15a6a0095c1b25071a5691e7c901fd0b95857a96c0e4659b48716a" + publisherLookupKey := "b6ee086390c280eeb9824c331a4427596f0c8510d5564bc1b6168d0059a46e2b" + firstAddedGranteeLookupKey := "a13678e81f9d939b9401a3ad7e548d2ceb81c50f8c76424296e83a1ad79c0df0" + secondAddedGranteeLookupKey := "d5e9a6499ca74f5b8b958a4b89b7338045b2baa9420e115443a8050e26986564" s := kvsmock.New() al := setupAccessLogic2() diff --git a/pkg/dynamicaccess/publish.go b/pkg/dynamicaccess/publish.go deleted file mode 100644 index f913288e4d5..00000000000 --- a/pkg/dynamicaccess/publish.go +++ /dev/null @@ -1,12 +0,0 @@ -package dynamicaccess - -type Publish interface { - upload(ref string) (string, error) -} - -type DefaultPublish struct { -} - -func (d *DefaultPublish) upload(ref string) (string, error) { - return "default", nil -} diff --git a/pkg/dynamicaccess/publish_test.go b/pkg/dynamicaccess/publish_test.go deleted file mode 100644 index d31069da4a3..00000000000 --- a/pkg/dynamicaccess/publish_test.go +++ /dev/null @@ -1,11 +0,0 @@ -package dynamicaccess - -import "testing" - -func TestUpload(t *testing.T) { - p := &DefaultPublish{} - _, err := p.upload("test") - if err != nil { - t.Errorf("Error uploading file: %v", err) - } -} diff --git a/pkg/dynamicaccess/timestamp.go b/pkg/dynamicaccess/timestamp.go deleted file mode 100644 index 48347d33a7c..00000000000 --- a/pkg/dynamicaccess/timestamp.go +++ /dev/null @@ -1,10 +0,0 @@ -package dynamicaccess - -// container interface bee-ből a manifest -type Timestamp interface{} - -type defaultTimeStamp struct{} - -func NewTimestamp() Timestamp { - return &defaultTimeStamp{} -} diff --git a/pkg/dynamicaccess/timestamp_test.go b/pkg/dynamicaccess/timestamp_test.go deleted file mode 100644 index e39ccbcf0c5..00000000000 --- a/pkg/dynamicaccess/timestamp_test.go +++ /dev/null @@ -1 +0,0 @@ -package dynamicaccess From 330fec8bf40eaa95a0745d53b83a6699d69004ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A1lint=20Ujv=C3=A1ri?= <58116288+bosi95@users.noreply.github.com> Date: Wed, 3 Apr 2024 14:05:25 +0200 Subject: [PATCH 20/34] Persist grantee list on swarm (#30) * Persist grantee list on swarm * accesslogic refactor * Refactor grantee list tests Co-authored-by: Roland Seres --- pkg/dynamicaccess/accesslogic.go | 5 +- pkg/dynamicaccess/controller.go | 6 +- pkg/dynamicaccess/controller_test.go | 4 +- pkg/dynamicaccess/grantee.go | 130 ++++++++++-- pkg/dynamicaccess/grantee_manager.go | 25 +-- pkg/dynamicaccess/grantee_manager_test.go | 6 +- pkg/dynamicaccess/grantee_test.go | 236 +++++++++++++++------- pkg/dynamicaccess/mock/container.go | 20 -- pkg/dynamicaccess/mock/grantee.go | 51 +++++ 9 files changed, 352 insertions(+), 131 deletions(-) delete mode 100644 pkg/dynamicaccess/mock/container.go create mode 100644 pkg/dynamicaccess/mock/grantee.go diff --git a/pkg/dynamicaccess/accesslogic.go b/pkg/dynamicaccess/accesslogic.go index 6c632a58351..593e02d630c 100644 --- a/pkg/dynamicaccess/accesslogic.go +++ b/pkg/dynamicaccess/accesslogic.go @@ -76,10 +76,11 @@ func (al ActLogic) AddGrantee(storage kvs.KeyValueStore, publisherPubKey, grante return err } lookupKey := keys[0] - accessKeyEncryptionKey := keys[1] + // accessKeyDecryptionKey is used for encryption of the access key + accessKeyDecryptionKey := keys[1] // Encrypt the access key for the new Grantee - cipher := encryption.New(encryption.Key(accessKeyEncryptionKey), 0, uint32(0), hashFunc) + cipher := encryption.New(encryption.Key(accessKeyDecryptionKey), 0, uint32(0), hashFunc) granteeEncryptedAccessKey, err := cipher.Encrypt(accessKey) if err != nil { return err diff --git a/pkg/dynamicaccess/controller.go b/pkg/dynamicaccess/controller.go index d118e22489f..df39fa23c32 100644 --- a/pkg/dynamicaccess/controller.go +++ b/pkg/dynamicaccess/controller.go @@ -9,7 +9,7 @@ import ( type Controller interface { DownloadHandler(timestamp int64, enryptedRef swarm.Address, publisher *ecdsa.PublicKey, tag string) (swarm.Address, error) - UploadHandler(ref swarm.Address, publisher *ecdsa.PublicKey, topic string) (swarm.Address, error) + UploadHandler(ref swarm.Address, publisher *ecdsa.PublicKey) (swarm.Address, error) } type defaultController struct { @@ -27,7 +27,7 @@ func (c *defaultController) DownloadHandler(timestamp int64, enryptedRef swarm.A return addr, err } -func (c *defaultController) UploadHandler(ref swarm.Address, publisher *ecdsa.PublicKey, topic string) (swarm.Address, error) { +func (c *defaultController) UploadHandler(ref swarm.Address, publisher *ecdsa.PublicKey) (swarm.Address, error) { kvs, err := c.history.Lookup(0) if err != nil { return swarm.EmptyAddress, err @@ -36,7 +36,7 @@ func (c *defaultController) UploadHandler(ref swarm.Address, publisher *ecdsa.Pu // new feed // TODO: putter session to create kvs kvs = kvsmock.New() - _, err = c.granteeManager.Publish(kvs, publisher, topic) + _, err = c.granteeManager.Publish(kvs, publisher) if err != nil { return swarm.EmptyAddress, err } diff --git a/pkg/dynamicaccess/controller_test.go b/pkg/dynamicaccess/controller_test.go index 63f80059e08..24107123ab5 100644 --- a/pkg/dynamicaccess/controller_test.go +++ b/pkg/dynamicaccess/controller_test.go @@ -72,9 +72,9 @@ func TestEncrypt(t *testing.T) { eref, ref := prepareEncryptedChunkReference(ak) key1, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - gm.Add("topic", []*ecdsa.PublicKey{&key1.PublicKey}) + gm.Add([]*ecdsa.PublicKey{&key1.PublicKey}) - addr, _ := c.UploadHandler(ref, &pk.PublicKey, "topic") + addr, _ := c.UploadHandler(ref, &pk.PublicKey) if !addr.Equal(eref) { t.Fatalf("Decrypted chunk address: %s is not the expected: %s", addr, eref) } diff --git a/pkg/dynamicaccess/grantee.go b/pkg/dynamicaccess/grantee.go index d85ebf217ea..afbe5349818 100644 --- a/pkg/dynamicaccess/grantee.go +++ b/pkg/dynamicaccess/grantee.go @@ -1,44 +1,134 @@ package dynamicaccess import ( + "context" "crypto/ecdsa" + "crypto/elliptic" + "fmt" + + "github.com/ethersphere/bee/pkg/file" + "github.com/ethersphere/bee/pkg/storer" + "github.com/ethersphere/bee/pkg/swarm" +) + +const ( + publicKeyLen = 65 ) type GranteeList interface { - Add(topic string, addList []*ecdsa.PublicKey) error - Remove(topic string, removeList []*ecdsa.PublicKey) error - Get(topic string) []*ecdsa.PublicKey + Add(addList []*ecdsa.PublicKey) error + Remove(removeList []*ecdsa.PublicKey) error + Get() []*ecdsa.PublicKey + Save() (swarm.Address, error) } type GranteeListStruct struct { - grantees map[string][]*ecdsa.PublicKey + grantees []byte + loadSave file.LoadSaver + putter storer.PutterSession +} + +var _ GranteeList = (*GranteeListStruct)(nil) + +func (g *GranteeListStruct) Get() []*ecdsa.PublicKey { + return g.deserialize(g.grantees) +} + +func (g *GranteeListStruct) serialize(publicKeys []*ecdsa.PublicKey) []byte { + b := make([]byte, 0, len(publicKeys)*publicKeyLen) + for _, key := range publicKeys { + b = append(b, g.serializePublicKey(key)...) + } + return b +} + +func (g *GranteeListStruct) serializePublicKey(pub *ecdsa.PublicKey) []byte { + return elliptic.Marshal(pub.Curve, pub.X, pub.Y) } -func (g *GranteeListStruct) Get(topic string) []*ecdsa.PublicKey { - grantees := g.grantees[topic] - keys := make([]*ecdsa.PublicKey, len(grantees)) - copy(keys, grantees) - return keys +func (g *GranteeListStruct) deserialize(data []byte) []*ecdsa.PublicKey { + if len(data) == 0 { + return nil + } + + p := make([]*ecdsa.PublicKey, 0, len(data)/publicKeyLen) + for i := 0; i < len(data); i += publicKeyLen { + pubKey := g.deserializeBytes(data[i : i+publicKeyLen]) + if pubKey == nil { + return nil + } + p = append(p, pubKey) + } + return p } -func (g *GranteeListStruct) Add(topic string, addList []*ecdsa.PublicKey) error { - g.grantees[topic] = append(g.grantees[topic], addList...) +func (g *GranteeListStruct) deserializeBytes(data []byte) *ecdsa.PublicKey { + curve := elliptic.P256() + x, y := elliptic.Unmarshal(curve, data) + return &ecdsa.PublicKey{Curve: curve, X: x, Y: y} +} + +func (g *GranteeListStruct) Add(addList []*ecdsa.PublicKey) error { + if len(addList) == 0 { + return fmt.Errorf("no public key provided") + } + + data := g.serialize(addList) + g.grantees = append(g.grantees, data...) return nil } -func (g *GranteeListStruct) Remove(topic string, removeList []*ecdsa.PublicKey) error { - for _, remove := range removeList { - for i, grantee := range g.grantees[topic] { - if *grantee == *remove { - g.grantees[topic][i] = g.grantees[topic][len(g.grantees[topic])-1] - g.grantees[topic] = g.grantees[topic][:len(g.grantees[topic])-1] +func (g *GranteeListStruct) Save() (swarm.Address, error) { + refBytes, err := g.loadSave.Save(context.Background(), g.grantees) + if err != nil { + return swarm.ZeroAddress, fmt.Errorf("grantee save error: %w", err) + } + address := swarm.NewAddress(refBytes) + err = g.putter.Done(address) + if err != nil { + return swarm.ZeroAddress, err + } + return address, nil +} + +func (g *GranteeListStruct) Remove(keysToRemove []*ecdsa.PublicKey) error { + if len(keysToRemove) == 0 { + return fmt.Errorf("nothing to remove") + } + grantees := g.deserialize(g.grantees) + if grantees == nil { + return fmt.Errorf("no grantee found") + } + + for _, remove := range keysToRemove { + for i, grantee := range grantees { + if grantee.Equal(remove) { + grantees[i] = grantees[len(grantees)-1] + grantees = grantees[:len(grantees)-1] } } } - + g.grantees = g.serialize(grantees) return nil } -func NewGrantee() *GranteeListStruct { - return &GranteeListStruct{grantees: make(map[string][]*ecdsa.PublicKey)} +func NewGranteeList(ls file.LoadSaver, putter storer.PutterSession, reference swarm.Address) GranteeList { + var ( + data []byte + err error + ) + if swarm.ZeroAddress.Equal(reference) || swarm.EmptyAddress.Equal(reference) { + data = []byte{} + } else { + data, err = ls.Load(context.Background(), reference.Bytes()) + } + if err != nil { + return nil + } + + return &GranteeListStruct{ + grantees: data, + loadSave: ls, + putter: putter, + } } diff --git a/pkg/dynamicaccess/grantee_manager.go b/pkg/dynamicaccess/grantee_manager.go index 004a933b006..eaa9cc9ffd9 100644 --- a/pkg/dynamicaccess/grantee_manager.go +++ b/pkg/dynamicaccess/grantee_manager.go @@ -3,16 +3,17 @@ package dynamicaccess import ( "crypto/ecdsa" + "github.com/ethersphere/bee/pkg/dynamicaccess/mock" "github.com/ethersphere/bee/pkg/kvs" "github.com/ethersphere/bee/pkg/swarm" ) type GranteeManager interface { - Get(topic string) []*ecdsa.PublicKey - Add(topic string, addList []*ecdsa.PublicKey) error - Publish(kvs kvs.KeyValueStore, publisher *ecdsa.PublicKey, topic string) (swarm.Address, error) + Get() []*ecdsa.PublicKey + Add(addList []*ecdsa.PublicKey) error + Publish(kvs kvs.KeyValueStore, publisher *ecdsa.PublicKey) (swarm.Address, error) - // HandleGrantees(topic string, addList, removeList []*ecdsa.PublicKey) *Act + // HandleGrantees(addList, removeList []*ecdsa.PublicKey) *Act // Load(grantee Grantee) // Save() @@ -22,24 +23,24 @@ var _ GranteeManager = (*granteeManager)(nil) type granteeManager struct { accessLogic ActLogic - granteeList GranteeList + granteeList *mock.GranteeListStructMock } func NewGranteeManager(al ActLogic) *granteeManager { - return &granteeManager{accessLogic: al, granteeList: NewGrantee()} + return &granteeManager{accessLogic: al, granteeList: mock.NewGranteeList()} } -func (gm *granteeManager) Get(topic string) []*ecdsa.PublicKey { - return gm.granteeList.Get(topic) +func (gm *granteeManager) Get() []*ecdsa.PublicKey { + return gm.granteeList.Get() } -func (gm *granteeManager) Add(topic string, addList []*ecdsa.PublicKey) error { - return gm.granteeList.Add(topic, addList) +func (gm *granteeManager) Add(addList []*ecdsa.PublicKey) error { + return gm.granteeList.Add(addList) } -func (gm *granteeManager) Publish(kvs kvs.KeyValueStore, publisher *ecdsa.PublicKey, topic string) (swarm.Address, error) { +func (gm *granteeManager) Publish(kvs kvs.KeyValueStore, publisher *ecdsa.PublicKey) (swarm.Address, error) { err := gm.accessLogic.AddPublisher(kvs, publisher) - for _, grantee := range gm.granteeList.Get(topic) { + for _, grantee := range gm.granteeList.Get() { err = gm.accessLogic.AddGrantee(kvs, publisher, grantee, nil) } return swarm.EmptyAddress, err diff --git a/pkg/dynamicaccess/grantee_manager_test.go b/pkg/dynamicaccess/grantee_manager_test.go index 7ac43425e10..d458e007088 100644 --- a/pkg/dynamicaccess/grantee_manager_test.go +++ b/pkg/dynamicaccess/grantee_manager_test.go @@ -24,15 +24,15 @@ func TestAdd(t *testing.T) { id1, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) id2, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - err := m.Add("topic", []*ecdsa.PublicKey{&id1.PublicKey}) + err := m.Add([]*ecdsa.PublicKey{&id1.PublicKey}) if err != nil { t.Errorf("Add() returned an error") } - err = m.Add("topic", []*ecdsa.PublicKey{&id2.PublicKey}) + err = m.Add([]*ecdsa.PublicKey{&id2.PublicKey}) if err != nil { t.Errorf("Add() returned an error") } s := kvsmock.New() - m.Publish(s, &pub.PublicKey, "topic") + m.Publish(s, &pub.PublicKey) fmt.Println("") } diff --git a/pkg/dynamicaccess/grantee_test.go b/pkg/dynamicaccess/grantee_test.go index 7962690ccb4..5b7a9bc5402 100644 --- a/pkg/dynamicaccess/grantee_test.go +++ b/pkg/dynamicaccess/grantee_test.go @@ -1,104 +1,202 @@ package dynamicaccess_test import ( + "context" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" - "reflect" "testing" "github.com/ethersphere/bee/pkg/dynamicaccess" + "github.com/ethersphere/bee/pkg/file" + "github.com/ethersphere/bee/pkg/file/loadsave" + "github.com/ethersphere/bee/pkg/file/pipeline" + "github.com/ethersphere/bee/pkg/file/pipeline/builder" + "github.com/ethersphere/bee/pkg/file/redundancy" + "github.com/ethersphere/bee/pkg/storage" + mockstorer "github.com/ethersphere/bee/pkg/storer/mock" + "github.com/ethersphere/bee/pkg/swarm" + "github.com/stretchr/testify/assert" ) -var _ dynamicaccess.GranteeList = (*dynamicaccess.GranteeListStruct)(nil) +var mockStorer = mockstorer.New() -func TestGranteeAddGrantees(t *testing.T) { - grantee := dynamicaccess.NewGrantee() - - key1, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - if err != nil { - t.Errorf("Expected no error, got %v", err) - } - - key2, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - if err != nil { - t.Errorf("Expected no error, got %v", err) - } - - addList := []*ecdsa.PublicKey{&key1.PublicKey, &key2.PublicKey} - exampleTopic := "topic" - err = grantee.Add(exampleTopic, addList) - - if err != nil { - t.Errorf("Expected no error, got %v", err) - } - - grantees := grantee.Get(exampleTopic) - if !reflect.DeepEqual(grantees, addList) { - t.Errorf("Expected grantees %v, got %v", addList, grantees) +func requestPipelineFactory(ctx context.Context, s storage.Putter, encrypt bool, rLevel redundancy.Level) func() pipeline.Interface { + return func() pipeline.Interface { + return builder.NewPipelineBuilder(ctx, s, encrypt, rLevel) } } -func TestRemoveGrantees(t *testing.T) { - grantee := dynamicaccess.NewGrantee() +func createLs() file.LoadSaver { + return loadsave.New(mockStorer.ChunkStore(), mockStorer.Cache(), requestPipelineFactory(context.Background(), mockStorer.Cache(), false, redundancy.NONE)) +} +func generateKeyListFixture() ([]*ecdsa.PublicKey, error) { key1, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - if err != nil { - t.Errorf("Expected no error, got %v", err) - } - key2, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + key3, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { - t.Errorf("Expected no error, got %v", err) - } - - addList := []*ecdsa.PublicKey{&key1.PublicKey, &key2.PublicKey} - exampleTopic := "topic" - err = grantee.Add(exampleTopic, addList) - if err != nil { - t.Errorf("Expected no error, got %v", err) + return nil, err } + return []*ecdsa.PublicKey{&key1.PublicKey, &key2.PublicKey, &key3.PublicKey}, nil +} - removeList := []*ecdsa.PublicKey{&key1.PublicKey} - err = grantee.Remove(exampleTopic, removeList) +func TestGranteeAddGet(t *testing.T) { + putter := mockStorer.DirectUpload() + gl := dynamicaccess.NewGranteeList(createLs(), putter, swarm.ZeroAddress) + keys, err := generateKeyListFixture() if err != nil { - t.Errorf("Expected no error, got %v", err) + t.Errorf("key generation error: %v", err) } - grantees := grantee.Get(exampleTopic) - expectedGrantees := []*ecdsa.PublicKey{&key2.PublicKey} + t.Run("Get empty grantee list should return error", func(t *testing.T) { + val := gl.Get() + assert.Nil(t, val) + }) + + t.Run("Get should return value equal to put value", func(t *testing.T) { + var ( + addList1 []*ecdsa.PublicKey = []*ecdsa.PublicKey{keys[0]} + addList2 []*ecdsa.PublicKey = []*ecdsa.PublicKey{keys[1], keys[0]} + addList3 []*ecdsa.PublicKey = keys + ) + testCases := []struct { + name string + list []*ecdsa.PublicKey + }{ + { + name: "Test list = 1", + list: addList1, + }, + { + name: "Test list = 2", + list: addList2, + }, + { + name: "Test list = 3", + list: addList3, + }, + { + name: "Test empty add list", + list: nil, + }, + } - for i, grantee := range grantees { - if grantee != expectedGrantees[i] { - t.Errorf("Expected grantee %v, got %v", expectedGrantees[i], grantee) + expList := []*ecdsa.PublicKey{} + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := gl.Add(tc.list) + if tc.list == nil { + assert.Error(t, err) + } else { + assert.NoError(t, err) + expList = append(expList, tc.list...) + retVal := gl.Get() + assert.Equal(t, expList, retVal) + } + }) } - } + }) } -func TestGetGrantees(t *testing.T) { - grantee := dynamicaccess.NewGrantee() - - key1, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) +func TestGranteeRemove(t *testing.T) { + putter := mockStorer.DirectUpload() + gl := dynamicaccess.NewGranteeList(createLs(), putter, swarm.ZeroAddress) + keys, err := generateKeyListFixture() if err != nil { - t.Errorf("Expected no error, got %v", err) + t.Errorf("key generation error: %v", err) } - key2, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - if err != nil { - t.Errorf("Expected no error, got %v", err) - } + t.Run("Add should NOT return error", func(t *testing.T) { + err := gl.Add(keys) + assert.NoError(t, err) + retVal := gl.Get() + assert.Equal(t, keys, retVal) + }) + removeList1 := []*ecdsa.PublicKey{keys[0]} + removeList2 := []*ecdsa.PublicKey{keys[2], keys[1]} + t.Run("Remove the first item should return NO error", func(t *testing.T) { + err := gl.Remove(removeList1) + assert.NoError(t, err) + retVal := gl.Get() + assert.Equal(t, removeList2, retVal) + }) + t.Run("Remove non-existent item should return NO error", func(t *testing.T) { + err := gl.Remove(removeList1) + assert.NoError(t, err) + retVal := gl.Get() + assert.Equal(t, removeList2, retVal) + }) + t.Run("Remove second and third item should return NO error", func(t *testing.T) { + err := gl.Remove(removeList2) + assert.NoError(t, err) + retVal := gl.Get() + assert.Nil(t, retVal) + }) + t.Run("Remove from empty grantee list should return error", func(t *testing.T) { + err := gl.Remove(removeList1) + assert.Error(t, err) + retVal := gl.Get() + assert.Nil(t, retVal) + }) + t.Run("Remove empty remove list should return error", func(t *testing.T) { + err := gl.Remove(nil) + assert.Error(t, err) + retVal := gl.Get() + assert.Nil(t, retVal) + }) +} - addList := []*ecdsa.PublicKey{&key1.PublicKey, &key2.PublicKey} - exampleTopic := "topic" - err = grantee.Add(exampleTopic, addList) +func TestGranteeSave(t *testing.T) { + keys, err := generateKeyListFixture() if err != nil { - t.Errorf("Expected no error, got %v", err) - } - - grantees := grantee.Get(exampleTopic) - for i, grantee := range grantees { - if grantee != addList[i] { - t.Errorf("Expected grantee %v, got %v", addList[i], grantee) - } + t.Errorf("key generation error: %v", err) } + t.Run("Save empty grantee list return NO error", func(t *testing.T) { + gl := dynamicaccess.NewGranteeList(createLs(), mockStorer.DirectUpload(), swarm.ZeroAddress) + _, err := gl.Save() + assert.NoError(t, err) + }) + t.Run("Save not empty grantee list return valid swarm address", func(t *testing.T) { + gl := dynamicaccess.NewGranteeList(createLs(), mockStorer.DirectUpload(), swarm.ZeroAddress) + err = gl.Add(keys) + ref, err := gl.Save() + assert.NoError(t, err) + assert.True(t, ref.IsValidNonEmpty()) + }) + t.Run("Save grantee list with one item, no error, pre-save value exist", func(t *testing.T) { + ls := createLs() + putter := mockStorer.DirectUpload() + gl1 := dynamicaccess.NewGranteeList(ls, putter, swarm.ZeroAddress) + + err := gl1.Add(keys) + assert.NoError(t, err) + + ref, err := gl1.Save() + assert.NoError(t, err) + + gl2 := dynamicaccess.NewGranteeList(ls, putter, ref) + val := gl2.Get() + assert.NoError(t, err) + assert.Equal(t, keys, val) + }) + t.Run("Save grantee list and add one item, no error, after-save value exist", func(t *testing.T) { + ls := createLs() + putter := mockStorer.DirectUpload() + + gl1 := dynamicaccess.NewGranteeList(ls, putter, swarm.ZeroAddress) + + err := gl1.Add(keys) + assert.NoError(t, err) + ref, err := gl1.Save() + assert.NoError(t, err) + + // New KVS + gl2 := dynamicaccess.NewGranteeList(ls, putter, ref) + err = gl2.Add(keys) + assert.NoError(t, err) + + val := gl2.Get() + assert.Equal(t, append(keys, keys...), val) + }) } diff --git a/pkg/dynamicaccess/mock/container.go b/pkg/dynamicaccess/mock/container.go deleted file mode 100644 index 3cad9badd39..00000000000 --- a/pkg/dynamicaccess/mock/container.go +++ /dev/null @@ -1,20 +0,0 @@ -package mock - -type ContainerMock struct { - AddFunc func(string, string, string) error - GetFunc func(string, string, string) (string, error) -} - -func (ma *ContainerMock) Add(ref string, publisher string, tag string) error { - if ma.AddFunc == nil { - return nil - } - return ma.AddFunc(ref, publisher, tag) -} - -func (ma *ContainerMock) Get(ref string, publisher string, tag string) (string, error) { - if ma.GetFunc == nil { - return "", nil - } - return ma.GetFunc(ref, publisher, tag) -} diff --git a/pkg/dynamicaccess/mock/grantee.go b/pkg/dynamicaccess/mock/grantee.go new file mode 100644 index 00000000000..f4bd90740ab --- /dev/null +++ b/pkg/dynamicaccess/mock/grantee.go @@ -0,0 +1,51 @@ +package mock + +import ( + "crypto/ecdsa" + + "github.com/ethersphere/bee/pkg/swarm" +) + +type GranteeListMock interface { + Add(publicKeys []*ecdsa.PublicKey) error + Remove(removeList []*ecdsa.PublicKey) error + Get() []*ecdsa.PublicKey + Save() (swarm.Address, error) +} + +type GranteeListStructMock struct { + grantees []*ecdsa.PublicKey +} + +func (g *GranteeListStructMock) Get() []*ecdsa.PublicKey { + grantees := g.grantees + keys := make([]*ecdsa.PublicKey, len(grantees)) + copy(keys, grantees) + return keys +} + +func (g *GranteeListStructMock) Add(addList []*ecdsa.PublicKey) error { + g.grantees = append(g.grantees, addList...) + return nil +} + +func (g *GranteeListStructMock) Remove(removeList []*ecdsa.PublicKey) error { + for _, remove := range removeList { + for i, grantee := range g.grantees { + if *grantee == *remove { + g.grantees[i] = g.grantees[len(g.grantees)-1] + g.grantees = g.grantees[:len(g.grantees)-1] + } + } + } + + return nil +} + +func (g *GranteeListStructMock) Save() (swarm.Address, error) { + return swarm.EmptyAddress, nil +} + +func NewGranteeList() *GranteeListStructMock { + return &GranteeListStructMock{grantees: []*ecdsa.PublicKey{}} +} From 58a89155788a575173d450b9bd13bcaf4bcd10b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferenc=20S=C3=A1rai?= Date: Thu, 4 Apr 2024 11:41:44 +0200 Subject: [PATCH 21/34] Update package imports to use the v2 version of the modules (#33) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ferenc Sárai --- pkg/dynamicaccess/accesslogic.go | 6 +++--- pkg/dynamicaccess/accesslogic_test.go | 8 ++++---- pkg/dynamicaccess/controller.go | 4 ++-- pkg/dynamicaccess/controller_test.go | 12 ++++++------ pkg/dynamicaccess/grantee.go | 6 +++--- pkg/dynamicaccess/grantee_manager.go | 6 +++--- pkg/dynamicaccess/grantee_manager_test.go | 4 ++-- pkg/dynamicaccess/grantee_test.go | 18 +++++++++--------- pkg/dynamicaccess/history.go | 2 +- pkg/dynamicaccess/history_test.go | 6 +++--- pkg/dynamicaccess/mock/grantee.go | 2 +- pkg/dynamicaccess/mock/history.go | 10 +++++----- pkg/dynamicaccess/mock/session.go | 4 ++-- pkg/dynamicaccess/session.go | 4 ++-- pkg/dynamicaccess/session_test.go | 8 ++++---- pkg/kvs/kvs.go | 8 ++++---- pkg/kvs/kvs_test.go | 18 +++++++++--------- pkg/kvs/mock/kvs.go | 4 ++-- 18 files changed, 65 insertions(+), 65 deletions(-) diff --git a/pkg/dynamicaccess/accesslogic.go b/pkg/dynamicaccess/accesslogic.go index 593e02d630c..a2a6fbdce24 100644 --- a/pkg/dynamicaccess/accesslogic.go +++ b/pkg/dynamicaccess/accesslogic.go @@ -3,9 +3,9 @@ package dynamicaccess import ( "crypto/ecdsa" - encryption "github.com/ethersphere/bee/pkg/encryption" - "github.com/ethersphere/bee/pkg/kvs" - "github.com/ethersphere/bee/pkg/swarm" + encryption "github.com/ethersphere/bee/v2/pkg/encryption" + "github.com/ethersphere/bee/v2/pkg/kvs" + "github.com/ethersphere/bee/v2/pkg/swarm" "golang.org/x/crypto/sha3" ) diff --git a/pkg/dynamicaccess/accesslogic_test.go b/pkg/dynamicaccess/accesslogic_test.go index 8c2f2a6489f..be115f424d1 100644 --- a/pkg/dynamicaccess/accesslogic_test.go +++ b/pkg/dynamicaccess/accesslogic_test.go @@ -7,10 +7,10 @@ import ( "encoding/hex" "testing" - "github.com/ethersphere/bee/pkg/crypto" - "github.com/ethersphere/bee/pkg/dynamicaccess" - kvsmock "github.com/ethersphere/bee/pkg/kvs/mock" - "github.com/ethersphere/bee/pkg/swarm" + "github.com/ethersphere/bee/v2/pkg/crypto" + "github.com/ethersphere/bee/v2/pkg/dynamicaccess" + kvsmock "github.com/ethersphere/bee/v2/pkg/kvs/mock" + "github.com/ethersphere/bee/v2/pkg/swarm" ) // Generates a new test environment with a fix private key diff --git a/pkg/dynamicaccess/controller.go b/pkg/dynamicaccess/controller.go index df39fa23c32..ccdc8c2d7f4 100644 --- a/pkg/dynamicaccess/controller.go +++ b/pkg/dynamicaccess/controller.go @@ -3,8 +3,8 @@ package dynamicaccess import ( "crypto/ecdsa" - kvsmock "github.com/ethersphere/bee/pkg/kvs/mock" - "github.com/ethersphere/bee/pkg/swarm" + kvsmock "github.com/ethersphere/bee/v2/pkg/kvs/mock" + "github.com/ethersphere/bee/v2/pkg/swarm" ) type Controller interface { diff --git a/pkg/dynamicaccess/controller_test.go b/pkg/dynamicaccess/controller_test.go index 24107123ab5..efbfc8d4e42 100644 --- a/pkg/dynamicaccess/controller_test.go +++ b/pkg/dynamicaccess/controller_test.go @@ -8,12 +8,12 @@ import ( "testing" "time" - "github.com/ethersphere/bee/pkg/crypto" - "github.com/ethersphere/bee/pkg/dynamicaccess" - "github.com/ethersphere/bee/pkg/dynamicaccess/mock" - "github.com/ethersphere/bee/pkg/encryption" - kvsmock "github.com/ethersphere/bee/pkg/kvs/mock" - "github.com/ethersphere/bee/pkg/swarm" + "github.com/ethersphere/bee/v2/pkg/crypto" + "github.com/ethersphere/bee/v2/pkg/dynamicaccess" + "github.com/ethersphere/bee/v2/pkg/dynamicaccess/mock" + "github.com/ethersphere/bee/v2/pkg/encryption" + kvsmock "github.com/ethersphere/bee/v2/pkg/kvs/mock" + "github.com/ethersphere/bee/v2/pkg/swarm" "golang.org/x/crypto/sha3" ) diff --git a/pkg/dynamicaccess/grantee.go b/pkg/dynamicaccess/grantee.go index afbe5349818..d850fc047cc 100644 --- a/pkg/dynamicaccess/grantee.go +++ b/pkg/dynamicaccess/grantee.go @@ -6,9 +6,9 @@ import ( "crypto/elliptic" "fmt" - "github.com/ethersphere/bee/pkg/file" - "github.com/ethersphere/bee/pkg/storer" - "github.com/ethersphere/bee/pkg/swarm" + "github.com/ethersphere/bee/v2/pkg/file" + "github.com/ethersphere/bee/v2/pkg/storer" + "github.com/ethersphere/bee/v2/pkg/swarm" ) const ( diff --git a/pkg/dynamicaccess/grantee_manager.go b/pkg/dynamicaccess/grantee_manager.go index eaa9cc9ffd9..1fac35a38bd 100644 --- a/pkg/dynamicaccess/grantee_manager.go +++ b/pkg/dynamicaccess/grantee_manager.go @@ -3,9 +3,9 @@ package dynamicaccess import ( "crypto/ecdsa" - "github.com/ethersphere/bee/pkg/dynamicaccess/mock" - "github.com/ethersphere/bee/pkg/kvs" - "github.com/ethersphere/bee/pkg/swarm" + "github.com/ethersphere/bee/v2/pkg/dynamicaccess/mock" + "github.com/ethersphere/bee/v2/pkg/kvs" + "github.com/ethersphere/bee/v2/pkg/swarm" ) type GranteeManager interface { diff --git a/pkg/dynamicaccess/grantee_manager_test.go b/pkg/dynamicaccess/grantee_manager_test.go index d458e007088..bb01c13cd85 100644 --- a/pkg/dynamicaccess/grantee_manager_test.go +++ b/pkg/dynamicaccess/grantee_manager_test.go @@ -7,8 +7,8 @@ import ( "fmt" "testing" - "github.com/ethersphere/bee/pkg/dynamicaccess" - kvsmock "github.com/ethersphere/bee/pkg/kvs/mock" + "github.com/ethersphere/bee/v2/pkg/dynamicaccess" + kvsmock "github.com/ethersphere/bee/v2/pkg/kvs/mock" ) func setupAccessLogic(privateKey *ecdsa.PrivateKey) dynamicaccess.ActLogic { diff --git a/pkg/dynamicaccess/grantee_test.go b/pkg/dynamicaccess/grantee_test.go index 5b7a9bc5402..b644f5896c5 100644 --- a/pkg/dynamicaccess/grantee_test.go +++ b/pkg/dynamicaccess/grantee_test.go @@ -7,15 +7,15 @@ import ( "crypto/rand" "testing" - "github.com/ethersphere/bee/pkg/dynamicaccess" - "github.com/ethersphere/bee/pkg/file" - "github.com/ethersphere/bee/pkg/file/loadsave" - "github.com/ethersphere/bee/pkg/file/pipeline" - "github.com/ethersphere/bee/pkg/file/pipeline/builder" - "github.com/ethersphere/bee/pkg/file/redundancy" - "github.com/ethersphere/bee/pkg/storage" - mockstorer "github.com/ethersphere/bee/pkg/storer/mock" - "github.com/ethersphere/bee/pkg/swarm" + "github.com/ethersphere/bee/v2/pkg/dynamicaccess" + "github.com/ethersphere/bee/v2/pkg/file" + "github.com/ethersphere/bee/v2/pkg/file/loadsave" + "github.com/ethersphere/bee/v2/pkg/file/pipeline" + "github.com/ethersphere/bee/v2/pkg/file/pipeline/builder" + "github.com/ethersphere/bee/v2/pkg/file/redundancy" + "github.com/ethersphere/bee/v2/pkg/storage" + mockstorer "github.com/ethersphere/bee/v2/pkg/storer/mock" + "github.com/ethersphere/bee/v2/pkg/swarm" "github.com/stretchr/testify/assert" ) diff --git a/pkg/dynamicaccess/history.go b/pkg/dynamicaccess/history.go index 63ec00847a6..82d18f5ef8a 100644 --- a/pkg/dynamicaccess/history.go +++ b/pkg/dynamicaccess/history.go @@ -2,7 +2,7 @@ package dynamicaccess import ( "github.com/ethereum/go-ethereum/common" - "github.com/ethersphere/bee/pkg/kvs" + "github.com/ethersphere/bee/v2/pkg/kvs" ) type History interface { diff --git a/pkg/dynamicaccess/history_test.go b/pkg/dynamicaccess/history_test.go index c2164df7d39..6c0a48a4fd6 100644 --- a/pkg/dynamicaccess/history_test.go +++ b/pkg/dynamicaccess/history_test.go @@ -5,9 +5,9 @@ import ( "testing" "time" - "github.com/ethersphere/bee/pkg/dynamicaccess" - "github.com/ethersphere/bee/pkg/dynamicaccess/mock" - kvsmock "github.com/ethersphere/bee/pkg/kvs/mock" + "github.com/ethersphere/bee/v2/pkg/dynamicaccess" + "github.com/ethersphere/bee/v2/pkg/dynamicaccess/mock" + kvsmock "github.com/ethersphere/bee/v2/pkg/kvs/mock" "github.com/stretchr/testify/assert" ) diff --git a/pkg/dynamicaccess/mock/grantee.go b/pkg/dynamicaccess/mock/grantee.go index f4bd90740ab..50689ade08f 100644 --- a/pkg/dynamicaccess/mock/grantee.go +++ b/pkg/dynamicaccess/mock/grantee.go @@ -3,7 +3,7 @@ package mock import ( "crypto/ecdsa" - "github.com/ethersphere/bee/pkg/swarm" + "github.com/ethersphere/bee/v2/pkg/swarm" ) type GranteeListMock interface { diff --git a/pkg/dynamicaccess/mock/history.go b/pkg/dynamicaccess/mock/history.go index 91b544a0760..e7747434d59 100644 --- a/pkg/dynamicaccess/mock/history.go +++ b/pkg/dynamicaccess/mock/history.go @@ -5,11 +5,11 @@ import ( "sort" "time" - "github.com/ethersphere/bee/pkg/crypto" - "github.com/ethersphere/bee/pkg/feeds" - "github.com/ethersphere/bee/pkg/kvs" - "github.com/ethersphere/bee/pkg/storage" - "github.com/ethersphere/bee/pkg/swarm" + "github.com/ethersphere/bee/v2/pkg/crypto" + "github.com/ethersphere/bee/v2/pkg/feeds" + "github.com/ethersphere/bee/v2/pkg/kvs" + "github.com/ethersphere/bee/v2/pkg/storage" + "github.com/ethersphere/bee/v2/pkg/swarm" ) type historyMock struct { diff --git a/pkg/dynamicaccess/mock/session.go b/pkg/dynamicaccess/mock/session.go index ba3e3f8c8f2..9613aacedfa 100644 --- a/pkg/dynamicaccess/mock/session.go +++ b/pkg/dynamicaccess/mock/session.go @@ -3,8 +3,8 @@ package mock import ( "crypto/ecdsa" - "github.com/ethersphere/bee/pkg/crypto" - "github.com/ethersphere/bee/pkg/keystore" + "github.com/ethersphere/bee/v2/pkg/crypto" + "github.com/ethersphere/bee/v2/pkg/keystore" ) type SessionMock struct { diff --git a/pkg/dynamicaccess/session.go b/pkg/dynamicaccess/session.go index b2718aedd67..0b58c1aa3dd 100644 --- a/pkg/dynamicaccess/session.go +++ b/pkg/dynamicaccess/session.go @@ -4,8 +4,8 @@ import ( "crypto/ecdsa" "errors" - "github.com/ethersphere/bee/pkg/crypto" - "github.com/ethersphere/bee/pkg/keystore" + "github.com/ethersphere/bee/v2/pkg/crypto" + "github.com/ethersphere/bee/v2/pkg/keystore" ) // Session represents an interface for a Diffie-Helmann key derivation diff --git a/pkg/dynamicaccess/session_test.go b/pkg/dynamicaccess/session_test.go index 501d1abd2b6..493b998a805 100644 --- a/pkg/dynamicaccess/session_test.go +++ b/pkg/dynamicaccess/session_test.go @@ -8,10 +8,10 @@ import ( "io" "testing" - "github.com/ethersphere/bee/pkg/crypto" - "github.com/ethersphere/bee/pkg/dynamicaccess" - "github.com/ethersphere/bee/pkg/dynamicaccess/mock" - memkeystore "github.com/ethersphere/bee/pkg/keystore/mem" + "github.com/ethersphere/bee/v2/pkg/crypto" + "github.com/ethersphere/bee/v2/pkg/dynamicaccess" + "github.com/ethersphere/bee/v2/pkg/dynamicaccess/mock" + memkeystore "github.com/ethersphere/bee/v2/pkg/keystore/mem" ) func mockKeyFunc(publicKey *ecdsa.PublicKey, nonces [][]byte) ([][]byte, error) { diff --git a/pkg/kvs/kvs.go b/pkg/kvs/kvs.go index 1106f2ceee8..fcb5fc668bb 100644 --- a/pkg/kvs/kvs.go +++ b/pkg/kvs/kvs.go @@ -9,10 +9,10 @@ import ( "encoding/hex" "errors" - "github.com/ethersphere/bee/pkg/file" - "github.com/ethersphere/bee/pkg/manifest" - "github.com/ethersphere/bee/pkg/storer" - "github.com/ethersphere/bee/pkg/swarm" + "github.com/ethersphere/bee/v2/pkg/file" + "github.com/ethersphere/bee/v2/pkg/manifest" + "github.com/ethersphere/bee/v2/pkg/storer" + "github.com/ethersphere/bee/v2/pkg/swarm" ) type KeyValueStore interface { diff --git a/pkg/kvs/kvs_test.go b/pkg/kvs/kvs_test.go index 309250dacff..9edfe48062c 100644 --- a/pkg/kvs/kvs_test.go +++ b/pkg/kvs/kvs_test.go @@ -8,15 +8,15 @@ import ( "context" "testing" - "github.com/ethersphere/bee/pkg/file" - "github.com/ethersphere/bee/pkg/file/loadsave" - "github.com/ethersphere/bee/pkg/file/pipeline" - "github.com/ethersphere/bee/pkg/file/pipeline/builder" - "github.com/ethersphere/bee/pkg/file/redundancy" - "github.com/ethersphere/bee/pkg/kvs" - "github.com/ethersphere/bee/pkg/storage" - mockstorer "github.com/ethersphere/bee/pkg/storer/mock" - "github.com/ethersphere/bee/pkg/swarm" + "github.com/ethersphere/bee/v2/pkg/file" + "github.com/ethersphere/bee/v2/pkg/file/loadsave" + "github.com/ethersphere/bee/v2/pkg/file/pipeline" + "github.com/ethersphere/bee/v2/pkg/file/pipeline/builder" + "github.com/ethersphere/bee/v2/pkg/file/redundancy" + "github.com/ethersphere/bee/v2/pkg/kvs" + "github.com/ethersphere/bee/v2/pkg/storage" + mockstorer "github.com/ethersphere/bee/v2/pkg/storer/mock" + "github.com/ethersphere/bee/v2/pkg/swarm" "github.com/stretchr/testify/assert" ) diff --git a/pkg/kvs/mock/kvs.go b/pkg/kvs/mock/kvs.go index fc091a88417..78282934bf2 100644 --- a/pkg/kvs/mock/kvs.go +++ b/pkg/kvs/mock/kvs.go @@ -4,8 +4,8 @@ import ( "encoding/hex" "sync" - "github.com/ethersphere/bee/pkg/kvs" - "github.com/ethersphere/bee/pkg/swarm" + "github.com/ethersphere/bee/v2/pkg/kvs" + "github.com/ethersphere/bee/v2/pkg/swarm" ) var lock = &sync.Mutex{} From ce408acb03e4d1a4da3edf5e91a85914c0cdb833 Mon Sep 17 00:00:00 2001 From: Levente Kiss Date: Tue, 9 Apr 2024 16:23:36 +0200 Subject: [PATCH 22/34] chore(mantaray): merge mantaray fix for rebase --- pkg/dynamicaccess/history.go | 147 +++++++++++++++++++++++++--- pkg/dynamicaccess/history_test.go | 153 +++++++++++++++++++++--------- pkg/dynamicaccess/mock/history.go | 90 ------------------ pkg/manifest/mantaray.go | 24 +++-- 4 files changed, 255 insertions(+), 159 deletions(-) delete mode 100644 pkg/dynamicaccess/mock/history.go diff --git a/pkg/dynamicaccess/history.go b/pkg/dynamicaccess/history.go index 82d18f5ef8a..85a62085f36 100644 --- a/pkg/dynamicaccess/history.go +++ b/pkg/dynamicaccess/history.go @@ -1,36 +1,155 @@ package dynamicaccess import ( - "github.com/ethereum/go-ethereum/common" - "github.com/ethersphere/bee/v2/pkg/kvs" + "context" + "errors" + "fmt" + "math" + "strconv" + "time" + + "github.com/ethersphere/bee/v2/pkg/file" + "github.com/ethersphere/bee/v2/pkg/manifest" + "github.com/ethersphere/bee/v2/pkg/manifest/mantaray" + "github.com/ethersphere/bee/v2/pkg/swarm" ) type History interface { - Add(timestamp int64, kvs kvs.KeyValueStore) error - Get(timestamp int64) (kvs.KeyValueStore, error) - Lookup(at int64) (kvs.KeyValueStore, error) + Add(ctx context.Context, ref swarm.Address, timestamp *int64) error + Lookup(ctx context.Context, timestamp int64) (swarm.Address, error) + Store(ctx context.Context) (swarm.Address, error) } var _ History = (*history)(nil) +var ErrEndIteration = errors.New("end iteration") + type history struct { - history map[int64]*kvs.KeyValueStore + manifest *manifest.MantarayManifest + ls file.LoadSaver } -func NewHistory(topic []byte, owner common.Address) *history { - return &history{history: make(map[int64]*kvs.KeyValueStore)} +func NewHistory(ls file.LoadSaver, ref *swarm.Address) (*history, error) { + var err error + var m manifest.Interface + + if ref != nil { + m, err = manifest.NewDefaultManifestReference(*ref, ls) + } else { + m, err = manifest.NewDefaultManifest(ls, false) + } + if err != nil { + return nil, err + } + + mm, ok := m.(*manifest.MantarayManifest) + if !ok { + return nil, fmt.Errorf("expected MantarayManifest, got %T", m) + } + + return &history{manifest: mm, ls: ls}, nil } -func (h *history) Add(timestamp int64, kvs kvs.KeyValueStore) error { +func (h *history) Add(ctx context.Context, ref swarm.Address, timestamp *int64) error { + // Do we need any extra meta/act? + meta := map[string]string{} + // add timestamps transformed so that the latests timestamp becomes the smallest key + var unixTime int64 + if timestamp != nil { + unixTime = *timestamp + } else { + unixTime = time.Now().Unix() + } - return nil + key := strconv.FormatInt(math.MaxInt64-unixTime, 10) + return h.manifest.Add(ctx, key, manifest.NewEntry(ref, meta)) } -func (h *history) Lookup(at int64) (kvs.KeyValueStore, error) { - return nil, nil +// Lookup finds the entry for a path or returns error if not found +func (h *history) Lookup(ctx context.Context, timestamp int64) (swarm.Address, error) { + if timestamp <= 0 { + return swarm.ZeroAddress, errors.New("invalid timestamp") + } + + reversedTimestamp := math.MaxInt64 - timestamp + node, err := h.LookupNode(ctx, reversedTimestamp) + if err != nil { + return swarm.ZeroAddress, err + } + + if node != nil { + return swarm.NewAddress(node.Entry()), nil + } + + return swarm.ZeroAddress, nil } -func (h *history) Get(timestamp int64) (kvs.KeyValueStore, error) { - // get the feed +func (h *history) LookupNode(ctx context.Context, searchedTimestamp int64) (*mantaray.Node, error) { + // before node's timestamp is the closest one that is less than or equal to the searched timestamp + // for instance: 2030, 2020, 1994 -> search for 2021 -> before is 2020 + var beforeNode *mantaray.Node + // after node's timestamp is after the latest + // for instance: 2030, 2020, 1994 -> search for 1980 -> after is 1994 + var afterNode *mantaray.Node + + walker := func(pathTimestamp []byte, currNode *mantaray.Node, err error) error { + if err != nil { + return err + } + + if currNode.IsValueType() && len(currNode.Entry()) > 0 { + afterNode = currNode + + match, err := isBeforeMatch(pathTimestamp, searchedTimestamp) + if match { + beforeNode = currNode + // return error to stop the walk, this is how WalkNode works... + return ErrEndIteration + } + + return err + } + + return nil + } + + rootNode := h.manifest.Root() + err := rootNode.WalkNode(ctx, []byte{}, h.ls, walker) + + if err != nil && !errors.Is(err, ErrEndIteration) { + return nil, fmt.Errorf("history lookup node error: %w", err) + } + + if beforeNode != nil { + return beforeNode, nil + } + if afterNode != nil { + return afterNode, nil + + } return nil, nil } + +func (h *history) Store(ctx context.Context) (swarm.Address, error) { + return h.manifest.Store(ctx) +} + +func bytesToInt64(b []byte) (int64, error) { + num, err := strconv.ParseInt(string(b), 10, 64) + if err != nil { + return -1, err + } + + return num, nil +} + +func isBeforeMatch(pathTimestamp []byte, searchedTimestamp int64) (bool, error) { + targetTimestamp, err := bytesToInt64(pathTimestamp) + if err != nil { + return false, err + } + if targetTimestamp == 0 { + return false, nil + } + return searchedTimestamp <= targetTimestamp, nil +} diff --git a/pkg/dynamicaccess/history_test.go b/pkg/dynamicaccess/history_test.go index 6c0a48a4fd6..4b353823b00 100644 --- a/pkg/dynamicaccess/history_test.go +++ b/pkg/dynamicaccess/history_test.go @@ -1,59 +1,122 @@ package dynamicaccess_test import ( - "encoding/hex" + "context" "testing" "time" "github.com/ethersphere/bee/v2/pkg/dynamicaccess" - "github.com/ethersphere/bee/v2/pkg/dynamicaccess/mock" - kvsmock "github.com/ethersphere/bee/v2/pkg/kvs/mock" + "github.com/ethersphere/bee/v2/pkg/file/loadsave" + "github.com/ethersphere/bee/v2/pkg/file/pipeline" + "github.com/ethersphere/bee/v2/pkg/file/pipeline/builder" + "github.com/ethersphere/bee/v2/pkg/storage" + mockstorer "github.com/ethersphere/bee/v2/pkg/storer/mock" + "github.com/ethersphere/bee/v2/pkg/swarm" "github.com/stretchr/testify/assert" ) -func TestHistoryLookup(t *testing.T) { - h := prepareTestHistory() - now := time.Now() - - tests := []struct { - input int64 - expected string - }{ - {input: 0, expected: "value3"}, - {input: now.Unix(), expected: "value3"}, - {input: now.AddDate(0, -5, 0).Unix(), expected: "value3"}, - {input: now.AddDate(0, -6, 0).Unix(), expected: "value3"}, - {input: now.AddDate(-1, 0, 0).Unix(), expected: "value3"}, - {input: now.AddDate(-1, -6, 0).Unix(), expected: "value2"}, - {input: now.AddDate(-2, -0, 0).Unix(), expected: "value2"}, - {input: now.AddDate(-2, -6, 0).Unix(), expected: "value1"}, - {input: now.AddDate(-3, -0, 0).Unix(), expected: "value1"}, - } +func TestHistoryAdd(t *testing.T) { + h, err := dynamicaccess.NewHistory(nil, nil) + assert.NoError(t, err) - for _, tt := range tests { - t.Run("", func(t *testing.T) { - sAt, _ := h.Lookup(tt.input) - output, _ := sAt.Get([]byte("key1")) - assert.Equal(t, output, hex.EncodeToString([]byte(tt.expected))) - }) - } + addr := swarm.NewAddress([]byte("addr")) + + ctx := context.Background() + + err = h.Add(ctx, addr, nil) + assert.NoError(t, err) +} + +func TestSingleNodeHistoryLookup(t *testing.T) { + storer := mockstorer.New() + ctx := context.Background() + ls := loadsave.New(storer.ChunkStore(), storer.Cache(), pipelineFactory(storer.Cache(), false)) + + h, err := dynamicaccess.NewHistory(ls, nil) + assert.NoError(t, err) + + testActRef := swarm.RandAddress(t) + err = h.Add(ctx, testActRef, nil) + assert.NoError(t, err) + + _, err = h.Store(ctx) + assert.NoError(t, err) + + searchedTime := time.Now().Unix() + actRef, err := h.Lookup(ctx, searchedTime) + assert.NoError(t, err) + assert.True(t, actRef.Equal(testActRef)) +} + +func TestMultiNodeHistoryLookup(t *testing.T) { + storer := mockstorer.New() + ctx := context.Background() + ls := loadsave.New(storer.ChunkStore(), storer.Cache(), pipelineFactory(storer.Cache(), false)) + + h, _ := dynamicaccess.NewHistory(ls, nil) + + testActRef1 := swarm.NewAddress([]byte("39a5ea87b141fe44aa609c3327ecd891")) + firstTime := time.Date(1994, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() + h.Add(ctx, testActRef1, &firstTime) + + testActRef2 := swarm.NewAddress([]byte("39a5ea87b141fe44aa609c3327ecd892")) + secondTime := time.Date(2000, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() + h.Add(ctx, testActRef2, &secondTime) + + testActRef3 := swarm.NewAddress([]byte("39a5ea87b141fe44aa609c3327ecd893")) + thirdTime := time.Date(2015, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() + h.Add(ctx, testActRef3, &thirdTime) + + testActRef4 := swarm.NewAddress([]byte("39a5ea87b141fe44aa609c3327ecd894")) + fourthTime := time.Date(2020, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() + h.Add(ctx, testActRef4, &fourthTime) + + testActRef5 := swarm.NewAddress([]byte("39a5ea87b141fe44aa609c3327ecd895")) + fifthTime := time.Date(2030, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() + h.Add(ctx, testActRef5, &fifthTime) + + // latest + searchedTime := time.Date(1980, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() + actRef, err := h.Lookup(ctx, searchedTime) + assert.NoError(t, err) + assert.True(t, actRef.Equal(testActRef1)) + + // before first time + searchedTime = time.Date(2021, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() + actRef, err = h.Lookup(ctx, searchedTime) + assert.NoError(t, err) + assert.True(t, actRef.Equal(testActRef4)) + + // same time + searchedTime = time.Date(2000, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() + actRef, err = h.Lookup(ctx, searchedTime) + assert.NoError(t, err) + assert.True(t, actRef.Equal(testActRef2)) + + // after time + searchedTime = time.Date(2045, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() + actRef, err = h.Lookup(ctx, searchedTime) + assert.NoError(t, err) + assert.True(t, actRef.Equal(testActRef5)) } -func prepareTestHistory() dynamicaccess.History { - var ( - h = mock.NewHistory() - now = time.Now() - s1 = kvsmock.New() - s2 = kvsmock.New() - s3 = kvsmock.New() - ) - s1.Put([]byte("key1"), []byte("value1")) - s2.Put([]byte("key1"), []byte("value2")) - s3.Put([]byte("key1"), []byte("value3")) - - h.Insert(now.AddDate(-3, 0, 0).Unix(), s1) - h.Insert(now.AddDate(-2, 0, 0).Unix(), s2) - h.Insert(now.AddDate(-1, 0, 0).Unix(), s3) - - return h +func TestHistoryStore(t *testing.T) { + storer := mockstorer.New() + ctx := context.Background() + ls := loadsave.New(storer.ChunkStore(), storer.Cache(), pipelineFactory(storer.Cache(), false)) + + h, _ := dynamicaccess.NewHistory(ls, nil) + + testActRef1 := swarm.NewAddress([]byte("39a5ea87b141fe44aa609c3327ecd891")) + firstTime := time.Date(1994, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() + h.Add(ctx, testActRef1, &firstTime) + + _, err := h.Store(ctx) + assert.NoError(t, err) +} + +func pipelineFactory(s storage.Putter, encrypt bool) func() pipeline.Interface { + return func() pipeline.Interface { + return builder.NewPipelineBuilder(context.Background(), s, encrypt, 0) + } } diff --git a/pkg/dynamicaccess/mock/history.go b/pkg/dynamicaccess/mock/history.go deleted file mode 100644 index e7747434d59..00000000000 --- a/pkg/dynamicaccess/mock/history.go +++ /dev/null @@ -1,90 +0,0 @@ -package mock - -import ( - "context" - "sort" - "time" - - "github.com/ethersphere/bee/v2/pkg/crypto" - "github.com/ethersphere/bee/v2/pkg/feeds" - "github.com/ethersphere/bee/v2/pkg/kvs" - "github.com/ethersphere/bee/v2/pkg/storage" - "github.com/ethersphere/bee/v2/pkg/swarm" -) - -type historyMock struct { - history map[int64]kvs.KeyValueStore -} - -func NewHistory() *historyMock { - return &historyMock{history: make(map[int64]kvs.KeyValueStore)} -} - -func (h *historyMock) Add(timestamp int64, act kvs.KeyValueStore) error { - h.history[timestamp] = act - return nil -} - -func (h *historyMock) Insert(timestamp int64, act kvs.KeyValueStore) *historyMock { - h.Add(timestamp, act) - return h -} - -func (h *historyMock) Lookup(at int64) (kvs.KeyValueStore, error) { - keys := []int64{} - for k := range h.history { - keys = append(keys, k) - } - - sort.Slice(keys, func(i, j int) bool { return keys[i] < keys[j] }) - - timestamp := time.Now() - if at != 0 { - timestamp = time.Unix(at, 0) - } - - for i := len(keys) - 1; i >= 0; i-- { - update := time.Unix(keys[i], 0) - if update.Before(timestamp) || update.Equal(timestamp) { - return h.history[keys[i]], nil - } - } - return nil, nil -} - -func (h *historyMock) Get(timestamp int64) (kvs.KeyValueStore, error) { - return h.history[timestamp], nil -} - -type finder struct { - getter *feeds.Getter -} - -type updater struct { - *feeds.Putter - next uint64 -} - -func (f *finder) At(ctx context.Context, at int64, after uint64) (chunk swarm.Chunk, currentIndex, nextIndex feeds.Index, err error) { - return nil, nil, nil, nil -} - -func HistoryFinder(getter storage.Getter, feed *feeds.Feed) feeds.Lookup { - return &finder{feeds.NewGetter(getter, feed)} -} - -func (u *updater) Update(ctx context.Context, at int64, payload []byte) error { - return nil -} - -func (u *updater) Feed() *feeds.Feed { - return nil -} - -func HistoryUpdater(putter storage.Putter, signer crypto.Signer, topic []byte) (feeds.Updater, error) { - p, err := feeds.NewPutter(putter, signer, topic) - if err != nil { - return nil, err - } - return &updater{Putter: p}, nil -} diff --git a/pkg/manifest/mantaray.go b/pkg/manifest/mantaray.go index f3b86e06b66..009e3eab055 100644 --- a/pkg/manifest/mantaray.go +++ b/pkg/manifest/mantaray.go @@ -20,7 +20,7 @@ const ( ManifestMantarayContentType = "application/bzz-manifest-mantaray+octet-stream" ) -type mantarayManifest struct { +type MantarayManifest struct { trie *mantaray.Node ls file.LoadSaver @@ -31,7 +31,7 @@ func NewMantarayManifest( ls file.LoadSaver, encrypted bool, ) (Interface, error) { - mm := &mantarayManifest{ + mm := &MantarayManifest{ trie: mantaray.New(), ls: ls, } @@ -48,24 +48,28 @@ func NewMantarayManifestReference( reference swarm.Address, ls file.LoadSaver, ) (Interface, error) { - return &mantarayManifest{ + return &MantarayManifest{ trie: mantaray.NewNodeRef(reference.Bytes()), ls: ls, }, nil } -func (m *mantarayManifest) Type() string { +func (m *MantarayManifest) Root() *mantaray.Node { + return m.trie +} + +func (m *MantarayManifest) Type() string { return ManifestMantarayContentType } -func (m *mantarayManifest) Add(ctx context.Context, path string, entry Entry) error { +func (m *MantarayManifest) Add(ctx context.Context, path string, entry Entry) error { p := []byte(path) e := entry.Reference().Bytes() return m.trie.Add(ctx, p, e, entry.Metadata(), m.ls) } -func (m *mantarayManifest) Remove(ctx context.Context, path string) error { +func (m *MantarayManifest) Remove(ctx context.Context, path string) error { p := []byte(path) err := m.trie.Remove(ctx, p, m.ls) @@ -79,7 +83,7 @@ func (m *mantarayManifest) Remove(ctx context.Context, path string) error { return nil } -func (m *mantarayManifest) Lookup(ctx context.Context, path string) (Entry, error) { +func (m *MantarayManifest) Lookup(ctx context.Context, path string) (Entry, error) { p := []byte(path) node, err := m.trie.LookupNode(ctx, p, m.ls) @@ -100,13 +104,13 @@ func (m *mantarayManifest) Lookup(ctx context.Context, path string) (Entry, erro return entry, nil } -func (m *mantarayManifest) HasPrefix(ctx context.Context, prefix string) (bool, error) { +func (m *MantarayManifest) HasPrefix(ctx context.Context, prefix string) (bool, error) { p := []byte(prefix) return m.trie.HasPrefix(ctx, p, m.ls) } -func (m *mantarayManifest) Store(ctx context.Context, storeSizeFn ...StoreSizeFunc) (swarm.Address, error) { +func (m *MantarayManifest) Store(ctx context.Context, storeSizeFn ...StoreSizeFunc) (swarm.Address, error) { var ls mantaray.LoadSaver if len(storeSizeFn) > 0 { ls = &mantarayLoadSaver{ @@ -127,7 +131,7 @@ func (m *mantarayManifest) Store(ctx context.Context, storeSizeFn ...StoreSizeFu return address, nil } -func (m *mantarayManifest) IterateAddresses(ctx context.Context, fn swarm.AddressIterFunc) error { +func (m *MantarayManifest) IterateAddresses(ctx context.Context, fn swarm.AddressIterFunc) error { reference := swarm.NewAddress(m.trie.Reference()) if swarm.ZeroAddress.Equal(reference) { From 94b69e44cdb8eadf03f26f5e26fa92deb9c20f7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A1lint=20Ujv=C3=A1ri?= <58116288+bosi95@users.noreply.github.com> Date: Fri, 19 Apr 2024 13:03:25 +0200 Subject: [PATCH 23/34] Dynamicaccess service for ACT (#35) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add act.go with TODOs feat: Add Act interface feat: Add Marshal, Unmarshal skeleton feat: Refactor AccessType to iota feat: Add upload feat: Rename GenerateAccessControlManifest -> create feat: Add saltLengthIs32 feat: Add Mrshal, Unmarshal impl feat: Add Marshal Unmarshal feat: Remove ManifestEntry json annotations feat: Modify to public finc/method feat: Add ErrSaltLength Add pkg/dynamicaccess Refactor interfaces and implement default structs Refactor typo Refactor History package to use NewHistory() function Add Act interface and default implementation Add ACT use cases to act_ucs.md Add new files and implement interfaces, refactor packeges Update act_ucs.md base usecases Refactor access logic and add mock implementations*** Add DiffieHellman implementation and remove Keystore*** Refactor NewAccessLogic function Replace encryption.go to pkg/encryption Refactor packages Update act_ucs.md Update act_ucs.md Update act_ucs.md Update act_ucs.md Update act_ucs.md * Diffie-Hellman (#3) * Use DiffieHellmanMock * Adds a comment about Get * Add support for ECDSA public key in DiffieHellman.SharedSecret function * Update defaultAct implementation * Adds pseudo code for Access Logic * Update default Act creation; Fix basic Act tests * Refactor access logic to use new ActMock implementation * feat(history): test mockups wip * Refactor DiffieHellman implementation * changes pseudocode for Diffie-Hellmann read * Co-authored-by: Bálint Ujvári * DiffieHellman mock generates a real sherd secret * Refactor Act * Adds manifest lookup * Extend act_test * Adds unit tests, some values are mocked * Refactor act mock impl with map[string]map[string]string * Add check mock implementation for DiffieHellman interface * Add Load, Store to Act interface. Refactor Act interface * refactor act, diffieHellman mocks, tests * Add TestLoadStore function to act_test.go * Remove unnecessary code in Load function * Add history mock and History lookup test * Act refactor Co-authored-by: Bálint Ujvári * Refactor Add method to return Act interface * Change Get method return type to []byte --------- Co-authored-by: Ferenc Sárai Co-authored-by: Peter Ott Co-authored-by: Bálint Ujvári Co-authored-by: Levente Kiss Co-authored-by: Roland Seres Co-authored-by: Kexort Co-authored-by: Bálint Ujvári * Acces Logic (#8) * Use DiffieHellmanMock * Adds a comment about Get * Add support for ECDSA public key in DiffieHellman.SharedSecret function * Update defaultAct implementation * Adds pseudo code for Access Logic * Update default Act creation; Fix basic Act tests * Refactor access logic to use new ActMock implementation * feat(history): test mockups wip * Refactor DiffieHellman implementation * changes pseudocode for Diffie-Hellmann read * Co-authored-by: Bálint Ujvári * DiffieHellman mock generates a real sherd secret * Refactor Act * Adds manifest lookup * Extend act_test * Adds unit tests, some values are mocked * Refactor act mock impl with map[string]map[string]string * Add check mock implementation for DiffieHellman interface * started Add * changed some sig * save * new grantee addition handling * mod * changed helper function visibilities * some mod with grantee * test mod * save * no error in actInit * Add_New_Grantee_To_Content * comment * copied act_test.go * no compiler errors on our side * Adds Add_New_Grantee_To_Content and ActInit * almost complete grantee container * maybe complete grantee container * Solves merge conflict * access-logic-merge * fix merge issues * Added context & details to use cases (#6) ZH #106 Added context & details to use cases * Add grantee management (#10) * Add grantee management * Added controller test * Fix test fixture, refactor accesslogic * Add UploadHandler --------- Co-authored-by: Bálint Ujvári * (refactor): from `Get` to `Lookup` to improve clarity and consistency. The changes have been made in the `accesslogic.go`, `act.go`, `act_test.go`, `history_test.go`, and `mock/act.go` files. (#13) Co-authored-by: Ferenc Sárai * Act params rename doc (#14) * (refactor): ACT interface params + add doc comments * Revert "(refactor): ACT interface params + add doc comments" This reverts commit ee8da04fe7468a4fa65bd390fa17f72f2e93d301. * (refactor): ACT interface params + add doc comments * (refactor): Add error to ACT interface methods --------- Co-authored-by: Ferenc Sárai * Move and refactor ACT diffieHellman to Session. Add Key and NewFromKeystore functions. (#16) * Act swarm address (#15) * (refactor): ACT interface params + add doc comments * Revert "(refactor): ACT interface params + add doc comments" This reverts commit ee8da04fe7468a4fa65bd390fa17f72f2e93d301. * (refactor): ACT interface params + add doc comments * (refactor): Add error to ACT interface methods * Add in-memory storage and implement Store and Load methods * Move and refactor ACT diffieHellman to Session. Add Key and NewFromKeystore functions. --------- Co-authored-by: Ferenc Sárai Co-authored-by: Bálint Ujvári * (rename): defaultAct to inMemoryAct (#17) * (refactor): ACT interface params + add doc comments * Revert "(refactor): ACT interface params + add doc comments" This reverts commit ee8da04fe7468a4fa65bd390fa17f72f2e93d301. * (refactor): ACT interface params + add doc comments * (refactor): Add error to ACT interface methods * Add in-memory storage and implement Store and Load methods * *refactor) Rename defaultAct to inMemroryAct --------- Co-authored-by: Ferenc Sárai * (refactor): Update controller_test.go to use NewInMemoryAct, modify Session.Key to return correct dimensional byte slice (#18) * (refactor): Update controller_test.go to use NewInMemoryAct, modify Session.Key to return two-dimensional byte slice * (refactor:) Refactor session Key function to use append instead of index-based assignment --------- Co-authored-by: Ferenc Sárai * Act access logic merge (#19) * grantee container and access logc tests are passed * refactored access logic and grantee container * PR 19 comments resolving * Refactor * Refactor * Act kvs merge (#22) * grantee container and access logc tests are passed * refactored access logic and grantee container * PR 19 comments resolving * Refactor * Refactor * working manifest ACT with basic tests * (refactor:) Refactor act_test * (refactor:) Refactor kvs -> kvs.manifest, kvs.memory * (refactror:) kvs * refactor kvs contsructors --------- Co-authored-by: Roland Seres Co-authored-by: Bálint Ujvári Co-authored-by: Ferenc Sárai * Session refactor (#24) * pr comment fix * add comment to session.NewFromKeystore * Access logic refactor (#25) Refactors access logic --------- Co-authored-by: Peter Ott Co-authored-by: Ferenc Sárai Co-authored-by: Bálint Ujvári Co-authored-by: Peter Ott * (refactor:) PR comments (#23) * grantee-refactor * Dried up code, related to AddPublisher - AddNewGranteeToContent * Refactor * removed getEncryptedAccessKey * Renamed AddGrentees, RemoveGrantees, etc to Add, Remove, etc * (refactor:) PR comments * (refactor:) compile check * removed encrypted_ref, grantee check (validation) * changed interface * comments * some more comments * refactor kvs and add load and store * (refactor:) Use ref * renamed defaultGrantee to granteeList * removed null encrypted test in in TestGet_Error * refactor kvs: pass kvs IF argument instead of storing it * Refactor according to the result of the workshop * refactor kvs IF and mock * fix merge errors and Logic/get_error test * (test:) Add test for put/get after kvs.Save --------- Co-authored-by: Roland Seres Co-authored-by: Peter Ott Co-authored-by: Ferenc Sárai Co-authored-by: Bálint Ujvári Co-authored-by: Peter Ott * Add referenced mock kvs (#26) * add controller upload test * compile * Add test for grantee * Add Upload test * Implement controller logic, move grantee management * Act kvs test (#27) * (test:) Refactor tests * (fix:) Save reset counter --------- Co-authored-by: Ferenc Sárai * feat: add history lookup and add * feat: expose mantaray manifest * Small refactor + al test (#28) Adds TestDecryptRefWithGrantee_Success and replaces generateFixPrivateKey with getPrivKey Co-authored-by: Peter Ott * chore: tests + minor fixes * chore: minor test change * feat: history with reference * chore: debugging * Persist grantee list on swarm (#30) * Persist grantee list on swarm * accesslogic refactor * Refactor grantee list tests Co-authored-by: Roland Seres * Merging Swarm 2.0 master (#32) * fix(stamper): global lock stamper across multiple upload sessions (#4578) * fix: strategy and fetch timeout parsing (#4579) * feat: neighborhood suggester config (#4580) * feat: add codeql.yml (#4334) * feat: add reserveSizeWithinRadius to status protocol (#4585) * fix: missing 200 response (#4526) * feat: pinned reference integrity check API (#4573) * fix(redundancy/getter): wait for recovery and return error (#4581) * fix(pushsync): store the chunk locally when no peers are available fo… (#4597) * fix(redundancy): on by default when downloading (#4602) * fix: add missing openapi spec (#4598) * feat: bzz resource info API (#4588) * fix(redundancy): bzz unit test (#4603) * feat: redundancy ci (#4591) * chore: bump github.com/quic-go/quic-go from 0.38.1 to 0.38.2 (#4534) * feat: split input file to chunks with specified redundancy (#4600) * perf(getter): cancel inflight requests if enough chunks are fetched for recovery (#4608) * fix: store dir error info (#4605) * chore: remove repetitive words (#4611) * fix: use neighborhood suggester only on mainnet (#4612) * feat: alternative withdrawal address (#4606) * fix(seg65) (#4604) * fix(getter): redundancy getter cleanup (#4610) * feat: v2 (#4615) * fix(pin_integrity): changed route and added openapi (#4616) * fix: missing v2 in the makefile and goreleaser (#4622) * chore: package update * Update package imports to use the v2 version of the modules (#33) Co-authored-by: Ferenc Sárai * fix walkfn with key sort * feat: new option to walk nodes of mantaray in sequence * feat: add latest timestamp check * chore: uncomment wip stuff * chore: requested changes * test: fix to latest adjustment * Add ctrl logic * Add dac service * Continue add ACT handler * chore: use ZeroAddress * chore: make var name more general * connect api test with dac service * refactor ctrl based on history v2 * Fix: controller upload download flow + basic tests * hacked mock dac service for simple upload and download * Insert act uploadhandler into /bzz endpoint and remove uphandler * Refactor controller and api; enrypt and rLevel passed on during up/download * Connect Get,Head,Post endpoints with ACT * Add: act to devnode * devnode: close dac during shutdown * pass decrypted ref in r.ctx * set address ctx as swarm address * refactor: call actEncrpytionHandler in every endpoint * typo and comment fix in dynamicaccess * Add: mock dynamicaccess service and api tests * Add: TestDacEachEndpointWithAct; fixed some review comments * Add ACT head test for endpoints * CHG: first encrypt via ACT then upload normal reference * FIX: apiservice.dac nil error --------- Co-authored-by: Ferenc Sárai Co-authored-by: Ferenc Sárai Co-authored-by: Peter Ott Co-authored-by: Levente Kiss Co-authored-by: Roland Seres Co-authored-by: Kexort Co-authored-by: Bálint Ujvári Co-authored-by: András Arányi Co-authored-by: rolandlor <33499567+rolandlor@users.noreply.github.com> Co-authored-by: Peter Ott --- cmd/bee/cmd/start.go | 7 +- pkg/api/api.go | 10 + pkg/api/api_test.go | 7 + pkg/api/bytes.go | 47 +- pkg/api/bzz.go | 54 +- pkg/api/chunk.go | 28 +- pkg/api/chunk_address.go | 9 +- pkg/api/dirs.go | 13 +- pkg/api/dynamicaccess.go | 108 +++ pkg/api/dynamicaccess_test.go | 804 ++++++++++++++++++++++ pkg/api/export_test.go | 2 + pkg/api/feed.go | 19 +- pkg/api/router.go | 19 +- pkg/api/soc.go | 17 +- pkg/dynamicaccess/accesslogic.go | 40 +- pkg/dynamicaccess/accesslogic_test.go | 52 +- pkg/dynamicaccess/controller.go | 208 +++++- pkg/dynamicaccess/controller_test.go | 129 ++-- pkg/dynamicaccess/grantee.go | 10 +- pkg/dynamicaccess/grantee_manager.go | 47 -- pkg/dynamicaccess/grantee_manager_test.go | 38 - pkg/dynamicaccess/grantee_test.go | 9 +- pkg/dynamicaccess/mock/service.go | 162 +++++ pkg/dynamicaccess/service.go | 39 ++ pkg/kvs/kvs.go | 28 +- pkg/kvs/kvs_test.go | 51 +- pkg/kvs/mock/kvs.go | 7 +- pkg/node/devnode.go | 13 + pkg/node/node.go | 13 + pkg/soc/testing/soc.go | 36 + 30 files changed, 1707 insertions(+), 319 deletions(-) create mode 100644 pkg/api/dynamicaccess.go create mode 100644 pkg/api/dynamicaccess_test.go delete mode 100644 pkg/dynamicaccess/grantee_manager.go delete mode 100644 pkg/dynamicaccess/grantee_manager_test.go create mode 100644 pkg/dynamicaccess/mock/service.go create mode 100644 pkg/dynamicaccess/service.go diff --git a/cmd/bee/cmd/start.go b/cmd/bee/cmd/start.go index e659d125116..9403dc69571 100644 --- a/cmd/bee/cmd/start.go +++ b/cmd/bee/cmd/start.go @@ -27,6 +27,7 @@ import ( chaincfg "github.com/ethersphere/bee/v2/pkg/config" "github.com/ethersphere/bee/v2/pkg/crypto" "github.com/ethersphere/bee/v2/pkg/crypto/clef" + "github.com/ethersphere/bee/v2/pkg/dynamicaccess" "github.com/ethersphere/bee/v2/pkg/keystore" filekeystore "github.com/ethersphere/bee/v2/pkg/keystore/file" memkeystore "github.com/ethersphere/bee/v2/pkg/keystore/mem" @@ -293,7 +294,7 @@ func buildBeeNode(ctx context.Context, c *command, cmd *cobra.Command, logger lo neighborhoodSuggester = c.config.GetString(optionNameNeighborhoodSuggester) } - b, err := node.NewBee(ctx, c.config.GetString(optionNameP2PAddr), signerConfig.publicKey, signerConfig.signer, networkID, logger, signerConfig.libp2pPrivateKey, signerConfig.pssPrivateKey, &node.Options{ + b, err := node.NewBee(ctx, c.config.GetString(optionNameP2PAddr), signerConfig.publicKey, signerConfig.signer, networkID, logger, signerConfig.libp2pPrivateKey, signerConfig.pssPrivateKey, signerConfig.session, &node.Options{ DataDir: c.config.GetString(optionNameDataDir), CacheCapacity: c.config.GetUint64(optionNameCacheCapacity), DBOpenFilesLimit: c.config.GetUint64(optionNameDBOpenFilesLimit), @@ -373,6 +374,7 @@ type signerConfig struct { publicKey *ecdsa.PublicKey libp2pPrivateKey *ecdsa.PrivateKey pssPrivateKey *ecdsa.PrivateKey + session dynamicaccess.Session } func waitForClef(logger log.Logger, maxRetries uint64, endpoint string) (externalSigner *external.ExternalSigner, err error) { @@ -403,6 +405,7 @@ func (c *command) configureSigner(cmd *cobra.Command, logger log.Logger) (config var signer crypto.Signer var password string var publicKey *ecdsa.PublicKey + var session dynamicaccess.Session if p := c.config.GetString(optionNamePassword); p != "" { password = p } else if pf := c.config.GetString(optionNamePasswordFile); pf != "" { @@ -475,6 +478,7 @@ func (c *command) configureSigner(cmd *cobra.Command, logger log.Logger) (config } signer = crypto.NewDefaultSigner(swarmPrivateKey) publicKey = &swarmPrivateKey.PublicKey + session = dynamicaccess.NewDefaultSession(swarmPrivateKey) } logger.Info("swarm public key", "public_key", hex.EncodeToString(crypto.EncodeSecp256k1PublicKey(publicKey))) @@ -513,6 +517,7 @@ func (c *command) configureSigner(cmd *cobra.Command, logger log.Logger) (config publicKey: publicKey, libp2pPrivateKey: libp2pPrivateKey, pssPrivateKey: pssPrivateKey, + session: session, }, nil } diff --git a/pkg/api/api.go b/pkg/api/api.go index 72100cc0d9b..00373c28186 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -30,6 +30,7 @@ import ( "github.com/ethersphere/bee/v2/pkg/accounting" "github.com/ethersphere/bee/v2/pkg/auth" "github.com/ethersphere/bee/v2/pkg/crypto" + "github.com/ethersphere/bee/v2/pkg/dynamicaccess" "github.com/ethersphere/bee/v2/pkg/feeds" "github.com/ethersphere/bee/v2/pkg/file/pipeline" "github.com/ethersphere/bee/v2/pkg/file/pipeline/builder" @@ -85,6 +86,10 @@ const ( SwarmRedundancyFallbackModeHeader = "Swarm-Redundancy-Fallback-Mode" SwarmChunkRetrievalTimeoutHeader = "Swarm-Chunk-Retrieval-Timeout" SwarmLookAheadBufferSizeHeader = "Swarm-Lookahead-Buffer-Size" + SwarmActHeader = "Swarm-Act" + SwarmActTimestampHeader = "Swarm-Act-Timestamp" + SwarmActPublisherHeader = "Swarm-Act-Publisher" + SwarmActHistoryAddressHeader = "Swarm-Act-History-Address" ImmutableHeader = "Immutable" GasPriceHeader = "Gas-Price" @@ -117,6 +122,8 @@ var ( errBatchUnusable = errors.New("batch not usable") errUnsupportedDevNodeOperation = errors.New("operation not supported in dev mode") errOperationSupportedOnlyInFullMode = errors.New("operation is supported only in full mode") + errActDownload = errors.New("act download failed") + errActUpload = errors.New("act upload failed") ) // Storer interface provides the functionality required from the local storage @@ -147,6 +154,7 @@ type Service struct { feedFactory feeds.Factory signer crypto.Signer post postage.Service + dac dynamicaccess.Service postageContract postagecontract.Interface probe *Probe metricsRegistry *prometheus.Registry @@ -245,6 +253,7 @@ type ExtraOptions struct { Pss pss.Interface FeedFactory feeds.Factory Post postage.Service + Dac dynamicaccess.Service PostageContract postagecontract.Interface Staking staking.Contract Steward steward.Interface @@ -328,6 +337,7 @@ func (s *Service) Configure(signer crypto.Signer, auth auth.Authenticator, trace s.pss = e.Pss s.feedFactory = e.FeedFactory s.post = e.Post + s.dac = e.Dac s.postageContract = e.PostageContract s.steward = e.Steward s.stakingContract = e.Staking diff --git a/pkg/api/api_test.go b/pkg/api/api_test.go index 6a96812a908..b556d6b439f 100644 --- a/pkg/api/api_test.go +++ b/pkg/api/api_test.go @@ -28,6 +28,8 @@ import ( "github.com/ethersphere/bee/v2/pkg/auth" mockauth "github.com/ethersphere/bee/v2/pkg/auth/mock" "github.com/ethersphere/bee/v2/pkg/crypto" + "github.com/ethersphere/bee/v2/pkg/dynamicaccess" + mockdac "github.com/ethersphere/bee/v2/pkg/dynamicaccess/mock" "github.com/ethersphere/bee/v2/pkg/feeds" "github.com/ethersphere/bee/v2/pkg/file/pipeline" "github.com/ethersphere/bee/v2/pkg/file/pipeline/builder" @@ -102,6 +104,7 @@ type testServerOptions struct { PostageContract postagecontract.Interface StakingContract staking.Contract Post postage.Service + Dac dynamicaccess.Service Steward steward.Interface WsHeaders http.Header Authenticator auth.Authenticator @@ -152,6 +155,9 @@ func newTestServer(t *testing.T, o testServerOptions) (*http.Client, *websocket. if o.Post == nil { o.Post = mockpost.New() } + if o.Dac == nil { + o.Dac = mockdac.New() + } if o.BatchStore == nil { o.BatchStore = mockbatchstore.New(mockbatchstore.WithAcceptAllExistsFunc()) // default is with accept-all Exists() func } @@ -198,6 +204,7 @@ func newTestServer(t *testing.T, o testServerOptions) (*http.Client, *websocket. Pss: o.Pss, FeedFactory: o.Feeds, Post: o.Post, + Dac: o.Dac, PostageContract: o.PostageContract, Steward: o.Steward, SyncStatus: o.SyncStatus, diff --git a/pkg/api/bytes.go b/pkg/api/bytes.go index 9b5d9b902d6..dc3735a497b 100644 --- a/pkg/api/bytes.go +++ b/pkg/api/bytes.go @@ -33,12 +33,14 @@ func (s *Service) bytesUploadHandler(w http.ResponseWriter, r *http.Request) { defer span.Finish() headers := struct { - BatchID []byte `map:"Swarm-Postage-Batch-Id" validate:"required"` - SwarmTag uint64 `map:"Swarm-Tag"` - Pin bool `map:"Swarm-Pin"` - Deferred *bool `map:"Swarm-Deferred-Upload"` - Encrypt bool `map:"Swarm-Encrypt"` - RLevel redundancy.Level `map:"Swarm-Redundancy-Level"` + BatchID []byte `map:"Swarm-Postage-Batch-Id" validate:"required"` + SwarmTag uint64 `map:"Swarm-Tag"` + Pin bool `map:"Swarm-Pin"` + Deferred *bool `map:"Swarm-Deferred-Upload"` + Encrypt bool `map:"Swarm-Encrypt"` + RLevel redundancy.Level `map:"Swarm-Redundancy-Level"` + Act bool `map:"Swarm-Act"` + HistoryAddress *swarm.Address `map:"Swarm-Act-History-Address"` }{} if response := s.mapStructure(r.Header, &headers); response != nil { response("invalid header params", logger, w) @@ -100,7 +102,7 @@ func (s *Service) bytesUploadHandler(w http.ResponseWriter, r *http.Request) { } p := requestPipelineFn(putter, headers.Encrypt, headers.RLevel) - address, err := p(ctx, r.Body) + reference, err := p(ctx, r.Body) if err != nil { logger.Debug("split write all failed", "error", err) logger.Error(nil, "split write all failed") @@ -114,9 +116,18 @@ func (s *Service) bytesUploadHandler(w http.ResponseWriter, r *http.Request) { return } - span.SetTag("root_address", address) + encryptedReference := reference + if headers.Act { + encryptedReference, err = s.actEncryptionHandler(r.Context(), logger, w, putter, reference, headers.HistoryAddress) + if err != nil { + jsonhttp.InternalServerError(w, errActUpload) + return + } + } + // TODO: what should be the root_address ? (eref vs ref) + span.SetTag("root_address", reference) - err = putter.Done(address) + err = putter.Done(reference) if err != nil { logger.Debug("done split failed", "error", err) logger.Error(nil, "done split failed") @@ -133,7 +144,7 @@ func (s *Service) bytesUploadHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Expose-Headers", SwarmTagHeader) jsonhttp.Created(w, bytesPostResponse{ - Reference: address, + Reference: encryptedReference, }) } @@ -149,11 +160,16 @@ func (s *Service) bytesGetHandler(w http.ResponseWriter, r *http.Request) { return } + address := paths.Address + if v := getAddressFromContext(r.Context()); !v.Equal(swarm.ZeroAddress) { + address = v + } + additionalHeaders := http.Header{ ContentTypeHeader: {"application/octet-stream"}, } - s.downloadHandler(logger, w, r, paths.Address, additionalHeaders, true, false) + s.downloadHandler(logger, w, r, address, additionalHeaders, true, false) } func (s *Service) bytesHeadHandler(w http.ResponseWriter, r *http.Request) { @@ -167,11 +183,16 @@ func (s *Service) bytesHeadHandler(w http.ResponseWriter, r *http.Request) { return } + address := paths.Address + if v := getAddressFromContext(r.Context()); !v.Equal(swarm.ZeroAddress) { + address = v + } + getter := s.storer.Download(true) - ch, err := getter.Get(r.Context(), paths.Address) + ch, err := getter.Get(r.Context(), address) if err != nil { - logger.Debug("get root chunk failed", "chunk_address", paths.Address, "error", err) + logger.Debug("get root chunk failed", "chunk_address", address, "error", err) logger.Error(nil, "get rook chunk failed") w.WriteHeader(http.StatusNotFound) return diff --git a/pkg/api/bzz.go b/pkg/api/bzz.go index 65ded851f12..241e30cf165 100644 --- a/pkg/api/bzz.go +++ b/pkg/api/bzz.go @@ -63,14 +63,16 @@ func (s *Service) bzzUploadHandler(w http.ResponseWriter, r *http.Request) { defer span.Finish() headers := struct { - ContentType string `map:"Content-Type,mimeMediaType" validate:"required"` - BatchID []byte `map:"Swarm-Postage-Batch-Id" validate:"required"` - SwarmTag uint64 `map:"Swarm-Tag"` - Pin bool `map:"Swarm-Pin"` - Deferred *bool `map:"Swarm-Deferred-Upload"` - Encrypt bool `map:"Swarm-Encrypt"` - IsDir bool `map:"Swarm-Collection"` - RLevel redundancy.Level `map:"Swarm-Redundancy-Level"` + ContentType string `map:"Content-Type,mimeMediaType" validate:"required"` + BatchID []byte `map:"Swarm-Postage-Batch-Id" validate:"required"` + SwarmTag uint64 `map:"Swarm-Tag"` + Pin bool `map:"Swarm-Pin"` + Deferred *bool `map:"Swarm-Deferred-Upload"` + Encrypt bool `map:"Swarm-Encrypt"` + IsDir bool `map:"Swarm-Collection"` + RLevel redundancy.Level `map:"Swarm-Redundancy-Level"` + Act bool `map:"Swarm-Act"` + HistoryAddress *swarm.Address `map:"Swarm-Act-History-Address"` }{} if response := s.mapStructure(r.Header, &headers); response != nil { response("invalid header params", logger, w) @@ -132,10 +134,10 @@ func (s *Service) bzzUploadHandler(w http.ResponseWriter, r *http.Request) { } if headers.IsDir || headers.ContentType == multiPartFormData { - s.dirUploadHandler(ctx, logger, span, ow, r, putter, r.Header.Get(ContentTypeHeader), headers.Encrypt, tag, headers.RLevel) + s.dirUploadHandler(ctx, logger, span, ow, r, putter, r.Header.Get(ContentTypeHeader), headers.Encrypt, tag, headers.RLevel, headers.Act, headers.HistoryAddress) return } - s.fileUploadHandler(ctx, logger, span, ow, r, putter, headers.Encrypt, tag, headers.RLevel) + s.fileUploadHandler(ctx, logger, span, ow, r, putter, headers.Encrypt, tag, headers.RLevel, headers.Act, headers.HistoryAddress) } // fileUploadResponse is returned when an HTTP request to upload a file is successful @@ -155,6 +157,8 @@ func (s *Service) fileUploadHandler( encrypt bool, tagID uint64, rLevel redundancy.Level, + act bool, + historyAddress *swarm.Address, ) { queries := struct { FileName string `map:"name" validate:"startsnotwith=/"` @@ -260,6 +264,15 @@ func (s *Service) fileUploadHandler( } logger.Debug("store", "manifest_reference", manifestReference) + encryptedReference := manifestReference + if act { + encryptedReference, err = s.actEncryptionHandler(r.Context(), logger, w, putter, manifestReference, historyAddress) + if err != nil { + jsonhttp.InternalServerError(w, errActUpload) + return + } + } + err = putter.Done(manifestReference) if err != nil { logger.Debug("done split failed", "error", err) @@ -268,7 +281,7 @@ func (s *Service) fileUploadHandler( ext.LogError(span, err, olog.String("action", "putter.Done")) return } - + // TODO: what should be the root_address ? (eref vs ref) span.LogFields(olog.Bool("success", true)) span.SetTag("root_address", manifestReference) @@ -276,10 +289,11 @@ func (s *Service) fileUploadHandler( w.Header().Set(SwarmTagHeader, fmt.Sprint(tagID)) span.SetTag("tagID", tagID) } - w.Header().Set(ETagHeader, fmt.Sprintf("%q", manifestReference.String())) + w.Header().Set(ETagHeader, fmt.Sprintf("%q", encryptedReference.String())) w.Header().Set("Access-Control-Expose-Headers", SwarmTagHeader) + // TODO: do we need to return reference as well ? jsonhttp.Created(w, bzzUploadResponse{ - Reference: manifestReference, + Reference: encryptedReference, }) } @@ -295,11 +309,16 @@ func (s *Service) bzzDownloadHandler(w http.ResponseWriter, r *http.Request) { return } + address := paths.Address + if v := getAddressFromContext(r.Context()); !v.Equal(swarm.ZeroAddress) { + address = v + } + if strings.HasSuffix(paths.Path, "/") { paths.Path = strings.TrimRight(paths.Path, "/") + "/" // NOTE: leave one slash if there was some. } - s.serveReference(logger, paths.Address, paths.Path, w, r, false) + s.serveReference(logger, address, paths.Path, w, r, false) } func (s *Service) bzzHeadHandler(w http.ResponseWriter, r *http.Request) { @@ -314,11 +333,16 @@ func (s *Service) bzzHeadHandler(w http.ResponseWriter, r *http.Request) { return } + address := paths.Address + if v := getAddressFromContext(r.Context()); !v.Equal(swarm.ZeroAddress) { + address = v + } + if strings.HasSuffix(paths.Path, "/") { paths.Path = strings.TrimRight(paths.Path, "/") + "/" // NOTE: leave one slash if there was some. } - s.serveReference(logger, paths.Address, paths.Path, w, r, true) + s.serveReference(logger, address, paths.Path, w, r, true) } func (s *Service) serveReference(logger log.Logger, address swarm.Address, pathVar string, w http.ResponseWriter, r *http.Request, headerOnly bool) { diff --git a/pkg/api/chunk.go b/pkg/api/chunk.go index a572cacdcdf..13738f70d39 100644 --- a/pkg/api/chunk.go +++ b/pkg/api/chunk.go @@ -30,8 +30,10 @@ func (s *Service) chunkUploadHandler(w http.ResponseWriter, r *http.Request) { logger := s.logger.WithName("post_chunk").Build() headers := struct { - BatchID []byte `map:"Swarm-Postage-Batch-Id" validate:"required"` - SwarmTag uint64 `map:"Swarm-Tag"` + BatchID []byte `map:"Swarm-Postage-Batch-Id" validate:"required"` + SwarmTag uint64 `map:"Swarm-Tag"` + Act bool `map:"Swarm-Act"` + HistoryAddress *swarm.Address `map:"Swarm-Act-History-Address"` }{} if response := s.mapStructure(r.Header, &headers); response != nil { response("invalid header params", logger, w) @@ -139,6 +141,15 @@ func (s *Service) chunkUploadHandler(w http.ResponseWriter, r *http.Request) { } } + encryptedReference := chunk.Address() + if headers.Act { + encryptedReference, err = s.actEncryptionHandler(r.Context(), logger, w, putter, chunk.Address(), headers.HistoryAddress) + if err != nil { + jsonhttp.InternalServerError(w, errActUpload) + return + } + } + err = putter.Put(r.Context(), chunk) if err != nil { logger.Debug("chunk upload: write chunk failed", "chunk_address", chunk.Address(), "error", err) @@ -165,7 +176,7 @@ func (s *Service) chunkUploadHandler(w http.ResponseWriter, r *http.Request) { } w.Header().Set("Access-Control-Expose-Headers", SwarmTagHeader) - jsonhttp.Created(w, chunkAddressResponse{Reference: chunk.Address()}) + jsonhttp.Created(w, chunkAddressResponse{Reference: encryptedReference}) } func (s *Service) chunkGetHandler(w http.ResponseWriter, r *http.Request) { @@ -192,15 +203,20 @@ func (s *Service) chunkGetHandler(w http.ResponseWriter, r *http.Request) { return } - chunk, err := s.storer.Download(cache).Get(r.Context(), paths.Address) + address := paths.Address + if v := getAddressFromContext(r.Context()); !v.Equal(swarm.ZeroAddress) { + address = v + } + + chunk, err := s.storer.Download(cache).Get(r.Context(), address) if err != nil { if errors.Is(err, storage.ErrNotFound) { - loggerV1.Debug("chunk not found", "address", paths.Address) + loggerV1.Debug("chunk not found", "address", address) jsonhttp.NotFound(w, "chunk not found") return } - logger.Debug("read chunk failed", "chunk_address", paths.Address, "error", err) + logger.Debug("read chunk failed", "chunk_address", address, "error", err) logger.Error(nil, "read chunk failed") jsonhttp.InternalServerError(w, "read chunk failed") return diff --git a/pkg/api/chunk_address.go b/pkg/api/chunk_address.go index 6f214a0ea03..c49c980c2e7 100644 --- a/pkg/api/chunk_address.go +++ b/pkg/api/chunk_address.go @@ -23,9 +23,14 @@ func (s *Service) hasChunkHandler(w http.ResponseWriter, r *http.Request) { return } - has, err := s.storer.ChunkStore().Has(r.Context(), paths.Address) + address := paths.Address + if v := getAddressFromContext(r.Context()); !v.Equal(swarm.ZeroAddress) { + address = v + } + + has, err := s.storer.ChunkStore().Has(r.Context(), address) if err != nil { - logger.Debug("has chunk failed", "chunk_address", paths.Address, "error", err) + logger.Debug("has chunk failed", "chunk_address", address, "error", err) jsonhttp.BadRequest(w, err) return } diff --git a/pkg/api/dirs.go b/pkg/api/dirs.go index f54a02807c9..1ec5e6cde40 100644 --- a/pkg/api/dirs.go +++ b/pkg/api/dirs.go @@ -47,6 +47,8 @@ func (s *Service) dirUploadHandler( encrypt bool, tag uint64, rLevel redundancy.Level, + act bool, + historyAddress *swarm.Address, ) { if r.Body == http.NoBody { logger.Error(nil, "request has no body") @@ -98,6 +100,15 @@ func (s *Service) dirUploadHandler( return } + encryptedReference := reference + if act { + encryptedReference, err = s.actEncryptionHandler(r.Context(), logger, w, putter, reference, historyAddress) + if err != nil { + jsonhttp.InternalServerError(w, errActUpload) + return + } + } + err = putter.Done(reference) if err != nil { logger.Debug("store dir failed", "error", err) @@ -113,7 +124,7 @@ func (s *Service) dirUploadHandler( } w.Header().Set("Access-Control-Expose-Headers", SwarmTagHeader) jsonhttp.Created(w, bzzUploadResponse{ - Reference: reference, + Reference: encryptedReference, }) } diff --git a/pkg/api/dynamicaccess.go b/pkg/api/dynamicaccess.go new file mode 100644 index 00000000000..7a05b1ad38c --- /dev/null +++ b/pkg/api/dynamicaccess.go @@ -0,0 +1,108 @@ +package api + +import ( + "context" + "crypto/ecdsa" + "net/http" + + "github.com/ethersphere/bee/v2/pkg/jsonhttp" + "github.com/ethersphere/bee/v2/pkg/log" + storer "github.com/ethersphere/bee/v2/pkg/storer" + "github.com/ethersphere/bee/v2/pkg/swarm" + "github.com/gorilla/mux" +) + +type addressKey struct{} + +// getAddressFromContext is a helper function to extract the address from the context +func getAddressFromContext(ctx context.Context) swarm.Address { + v, ok := ctx.Value(addressKey{}).(swarm.Address) + if ok { + return v + } + return swarm.ZeroAddress +} + +// setAddress sets the swarm address in the context +func setAddressInContext(ctx context.Context, address swarm.Address) context.Context { + return context.WithValue(ctx, addressKey{}, address) +} + +func (s *Service) actDecryptionHandler() func(h http.Handler) http.Handler { + return func(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + logger := s.logger.WithName("acthandler").Build() + paths := struct { + Address swarm.Address `map:"address,resolve" validate:"required"` + }{} + if response := s.mapStructure(mux.Vars(r), &paths); response != nil { + response("invalid path params", logger, w) + return + } + + headers := struct { + Timestamp *int64 `map:"Swarm-Act-Timestamp"` + Publisher *ecdsa.PublicKey `map:"Swarm-Act-Publisher"` + HistoryAddress *swarm.Address `map:"Swarm-Act-History-Address"` + }{} + if response := s.mapStructure(r.Header, &headers); response != nil { + response("invalid header params", logger, w) + return + } + + // Try to download the file wihtout decryption, if the act headers are not present + if headers.Publisher == nil || headers.Timestamp == nil || headers.HistoryAddress == nil { + h.ServeHTTP(w, r) + return + } + ctx := r.Context() + reference, err := s.dac.DownloadHandler(ctx, *headers.Timestamp, paths.Address, headers.Publisher, *headers.HistoryAddress) + if err != nil { + jsonhttp.InternalServerError(w, errActDownload) + return + } + h.ServeHTTP(w, r.WithContext(setAddressInContext(ctx, reference))) + }) + } + +} + +// TODO: is ctx needed in ctrl upload ? +func (s *Service) actEncryptionHandler( + ctx context.Context, + logger log.Logger, + w http.ResponseWriter, + putter storer.PutterSession, + reference swarm.Address, + historyAddress *swarm.Address, +) (swarm.Address, error) { + publisherPublicKey := &s.publicKey + kvsReference, historyReference, encryptedReference, err := s.dac.UploadHandler(ctx, reference, publisherPublicKey, historyAddress) + if err != nil { + logger.Debug("act failed to encrypt reference", "error", err) + logger.Error(nil, "act failed to encrypt reference") + return swarm.ZeroAddress, err + } + err = putter.Done(historyReference) + if err != nil { + logger.Debug("done split history failed", "error", err) + logger.Error(nil, "done split history failed") + return swarm.ZeroAddress, err + } + err = putter.Done(encryptedReference) + if err != nil { + logger.Debug("done split encrypted reference failed", "error", err) + logger.Error(nil, "done split encrypted reference failed") + return swarm.ZeroAddress, err + } + err = putter.Done(kvsReference) + if err != nil { + logger.Debug("done split kvs reference failed", "error", err) + logger.Error(nil, "done split kvs reference failed") + return swarm.ZeroAddress, err + } + + w.Header().Set(SwarmActHistoryAddressHeader, historyReference.String()) + + return encryptedReference, nil +} diff --git a/pkg/api/dynamicaccess_test.go b/pkg/api/dynamicaccess_test.go new file mode 100644 index 00000000000..4657b1f8bbe --- /dev/null +++ b/pkg/api/dynamicaccess_test.go @@ -0,0 +1,804 @@ +// Copyright 2020 The Swarm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package api_test + +import ( + "bytes" + "context" + "encoding/hex" + "fmt" + "io" + "net/http" + "strconv" + "strings" + "testing" + "time" + + "github.com/ethersphere/bee/v2/pkg/api" + "github.com/ethersphere/bee/v2/pkg/crypto" + "github.com/ethersphere/bee/v2/pkg/dynamicaccess" + mockdac "github.com/ethersphere/bee/v2/pkg/dynamicaccess/mock" + "github.com/ethersphere/bee/v2/pkg/file/loadsave" + "github.com/ethersphere/bee/v2/pkg/file/redundancy" + "github.com/ethersphere/bee/v2/pkg/jsonhttp" + "github.com/ethersphere/bee/v2/pkg/jsonhttp/jsonhttptest" + "github.com/ethersphere/bee/v2/pkg/log" + mockpost "github.com/ethersphere/bee/v2/pkg/postage/mock" + testingsoc "github.com/ethersphere/bee/v2/pkg/soc/testing" + mockstorer "github.com/ethersphere/bee/v2/pkg/storer/mock" + "github.com/ethersphere/bee/v2/pkg/swarm" + "gitlab.com/nolash/go-mockbytes" +) + +func prepareHistoryFixture(storer api.Storer) (dynamicaccess.History, swarm.Address) { + ctx := context.Background() + ls := loadsave.New(storer.ChunkStore(), storer.Cache(), pipelineFactory(storer.Cache(), false, redundancy.NONE)) + + h, _ := dynamicaccess.NewHistory(ls, nil) + + testActRef1 := swarm.NewAddress([]byte("39a5ea87b141fe44aa609c3327ecd891")) + firstTime := time.Date(1994, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() + h.Add(ctx, testActRef1, &firstTime) + + testActRef2 := swarm.NewAddress([]byte("39a5ea87b141fe44aa609c3327ecd892")) + secondTime := time.Date(2000, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() + h.Add(ctx, testActRef2, &secondTime) + + testActRef3 := swarm.NewAddress([]byte("39a5ea87b141fe44aa609c3327ecd893")) + thirdTime := time.Date(2015, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() + h.Add(ctx, testActRef3, &thirdTime) + + testActRef4 := swarm.NewAddress([]byte("39a5ea87b141fe44aa609c3327ecd894")) + fourthTime := time.Date(2020, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() + h.Add(ctx, testActRef4, &fourthTime) + + testActRef5 := swarm.NewAddress([]byte("39a5ea87b141fe44aa609c3327ecd895")) + fifthTime := time.Date(2030, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() + h.Add(ctx, testActRef5, &fifthTime) + + ref, _ := h.Store(ctx) + return h, ref +} + +// TODO: feed test +// nolint:paralleltest,tparallel +// TestDacWithoutActHeader [positive tests]: +// On each endpoint: upload w/ "Swarm-Act" header then download and check the decrypted data +func TestDacEachEndpointWithAct(t *testing.T) { + t.Parallel() + var ( + spk, _ = hex.DecodeString("a786dd84b61485de12146fd9c4c02d87e8fd95f0542765cb7fc3d2e428c0bcfa") + pk, _ = crypto.DecodeSecp256k1PrivateKey(spk) + publicKeyBytes = crypto.EncodeSecp256k1PublicKey(&pk.PublicKey) + publisher = hex.EncodeToString(publicKeyBytes) + testfile = "testfile1" + storerMock = mockstorer.New() + logger = log.Noop + now = time.Now().Unix() + chunk = swarm.NewChunk( + swarm.MustParseHexAddress("0025737be11979e91654dffd2be817ac1e52a2dadb08c97a7cef12f937e707bc"), + []byte{72, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 149, 179, 31, 244, 146, 247, 129, 123, 132, 248, 215, 77, 44, 47, 91, 248, 229, 215, 89, 156, 210, 243, 3, 110, 204, 74, 101, 119, 53, 53, 145, 188, 193, 153, 130, 197, 83, 152, 36, 140, 150, 209, 191, 214, 193, 4, 144, 121, 32, 45, 205, 220, 59, 227, 28, 43, 161, 51, 108, 14, 106, 180, 135, 2}, + ) + g = mockbytes.New(0, mockbytes.MockTypeStandard).WithModulus(255) + bytedata, _ = g.SequentialBytes(swarm.ChunkSize * 2) + tag, _ = storerMock.NewSession() + sch = testingsoc.GenerateMockSOCWithKey(t, []byte("foo"), pk) + dirdata = []byte("Lorem ipsum dolor sit amet") + socResource = func(owner, id, sig string) string { return fmt.Sprintf("/soc/%s/%s?sig=%s", owner, id, sig) } + ) + + tc := []struct { + name string + downurl string + upurl string + exphash string + data io.Reader + expdata []byte + contenttype string + resp struct { + Reference swarm.Address `json:"reference"` + } + }{ + { + name: "bzz", + upurl: "/bzz?name=sample.html", + downurl: "/bzz", + exphash: "a5df670544eaea29e61b19d8739faa4573b19e4426e58a173e51ed0b5e7e2ade", + resp: api.BzzUploadResponse{Reference: swarm.MustParseHexAddress("a5df670544eaea29e61b19d8739faa4573b19e4426e58a173e51ed0b5e7e2ade")}, + data: strings.NewReader(testfile), + expdata: []byte(testfile), + contenttype: "text/html; charset=utf-8", + }, + { + name: "bzz-dir", + upurl: "/bzz?name=ipsum/lorem.txt", + downurl: "/bzz", + exphash: "6561b2a744d2a8f276270585da22e092c07c56624af83ac9969d52b54e87cee6/ipsum/lorem.txt", + resp: api.BzzUploadResponse{Reference: swarm.MustParseHexAddress("6561b2a744d2a8f276270585da22e092c07c56624af83ac9969d52b54e87cee6")}, + data: tarFiles(t, []f{ + { + data: dirdata, + name: "lorem.txt", + dir: "ipsum", + header: http.Header{ + api.ContentTypeHeader: {"text/plain; charset=utf-8"}, + }, + }, + }), + expdata: dirdata, + contenttype: api.ContentTypeTar, + }, + { + name: "bytes", + upurl: "/bytes", + downurl: "/bytes", + exphash: "e30da540bb9e1901169977fcf617f28b7f8df4537de978784f6d47491619a630", + resp: api.BytesPostResponse{Reference: swarm.MustParseHexAddress("e30da540bb9e1901169977fcf617f28b7f8df4537de978784f6d47491619a630")}, + data: bytes.NewReader(bytedata), + expdata: bytedata, + contenttype: "application/octet-stream", + }, + { + name: "chunks", + upurl: "/chunks", + downurl: "/chunks", + exphash: "ca8d2d29466e017cba46d383e7e0794d99a141185ec525086037f25fc2093155", + resp: api.ChunkAddressResponse{Reference: swarm.MustParseHexAddress("ca8d2d29466e017cba46d383e7e0794d99a141185ec525086037f25fc2093155")}, + data: bytes.NewReader(chunk.Data()), + expdata: chunk.Data(), + contenttype: "binary/octet-stream", + }, + { + name: "soc", + upurl: socResource(hex.EncodeToString(sch.Owner), hex.EncodeToString(sch.ID), hex.EncodeToString(sch.Signature)), + downurl: "/chunks", + exphash: "b100d7ce487426b17b98ff779fad4f2dd471d04ab1c8949dd2a1a78fe4a1524e", + resp: api.ChunkAddressResponse{Reference: swarm.MustParseHexAddress("b100d7ce487426b17b98ff779fad4f2dd471d04ab1c8949dd2a1a78fe4a1524e")}, + data: bytes.NewReader(sch.WrappedChunk.Data()), + expdata: sch.Chunk().Data(), + contenttype: "binary/octet-stream", + }, + } + + for _, v := range tc { + upTestOpts := []jsonhttptest.Option{ + jsonhttptest.WithRequestHeader(api.SwarmActHeader, "true"), + jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr), + jsonhttptest.WithRequestHeader(api.SwarmPinHeader, "true"), + jsonhttptest.WithRequestHeader(api.SwarmTagHeader, fmt.Sprintf("%d", tag.TagID)), + jsonhttptest.WithRequestBody(v.data), + jsonhttptest.WithExpectedJSONResponse(v.resp), + jsonhttptest.WithRequestHeader(api.ContentTypeHeader, v.contenttype), + } + if v.name == "soc" { + upTestOpts = append(upTestOpts, jsonhttptest.WithRequestHeader(api.SwarmPinHeader, "true")) + } else { + upTestOpts = append(upTestOpts, jsonhttptest.WithNonEmptyResponseHeader(api.SwarmTagHeader)) + } + expcontenttype := v.contenttype + if v.name == "bzz-dir" { + expcontenttype = "text/plain; charset=utf-8" + upTestOpts = append(upTestOpts, jsonhttptest.WithRequestHeader(api.SwarmCollectionHeader, "True")) + } + t.Run(v.name, func(t *testing.T) { + client, _, _, _ := newTestServer(t, testServerOptions{ + Storer: storerMock, + Logger: logger, + Post: mockpost.New(mockpost.WithAcceptAll()), + PublicKey: pk.PublicKey, + Dac: mockdac.New(), + }) + header := jsonhttptest.Request(t, client, http.MethodPost, v.upurl, http.StatusCreated, + upTestOpts..., + ) + + historyRef := header.Get(api.SwarmActHistoryAddressHeader) + jsonhttptest.Request(t, client, http.MethodGet, v.downurl+"/"+v.exphash, http.StatusOK, + jsonhttptest.WithRequestHeader(api.SwarmActTimestampHeader, strconv.FormatInt(now, 10)), + jsonhttptest.WithRequestHeader(api.SwarmActHistoryAddressHeader, historyRef), + jsonhttptest.WithRequestHeader(api.SwarmActPublisherHeader, publisher), + jsonhttptest.WithExpectedResponse(v.expdata), + jsonhttptest.WithExpectedContentLength(len(v.expdata)), + jsonhttptest.WithExpectedResponseHeader(api.ContentTypeHeader, expcontenttype), + ) + + if v.name != "bzz-dir" && v.name != "soc" && v.name != "chunks" { + t.Run("head", func(t *testing.T) { + jsonhttptest.Request(t, client, http.MethodHead, v.downurl+"/"+v.exphash, http.StatusOK, + jsonhttptest.WithRequestHeader(api.SwarmActTimestampHeader, strconv.FormatInt(now, 10)), + jsonhttptest.WithRequestHeader(api.SwarmActHistoryAddressHeader, historyRef), + jsonhttptest.WithRequestHeader(api.SwarmActPublisherHeader, publisher), + jsonhttptest.WithRequestBody(v.data), + jsonhttptest.WithExpectedContentLength(len(v.expdata)), + jsonhttptest.WithExpectedResponseHeader(api.ContentTypeHeader, expcontenttype), + ) + }) + } + }) + } +} + +// nolint:paralleltest,tparallel +// TestDacWithoutActHeader [negative tests]: +// 1. upload w/ "Swarm-Act" header then try to dowload w/o the header. +// 2. upload w/o "Swarm-Act" header then try to dowload w/ the header. +func TestDacWithoutAct(t *testing.T) { + t.Parallel() + var ( + spk, _ = hex.DecodeString("a786dd84b61485de12146fd9c4c02d87e8fd95f0542765cb7fc3d2e428c0bcfa") + pk, _ = crypto.DecodeSecp256k1PrivateKey(spk) + publicKeyBytes = crypto.EncodeSecp256k1PublicKey(&pk.PublicKey) + publisher = hex.EncodeToString(publicKeyBytes) + fileUploadResource = "/bzz" + fileDownloadResource = func(addr string) string { return "/bzz/" + addr } + storerMock = mockstorer.New() + h, fixtureHref = prepareHistoryFixture(storerMock) + logger = log.Noop + fileName = "sample.html" + now = time.Now().Unix() + ) + + t.Run("upload-w/-act-then-download-w/o-act", func(t *testing.T) { + client, _, _, _ := newTestServer(t, testServerOptions{ + Storer: storerMock, + Logger: logger, + Post: mockpost.New(mockpost.WithAcceptAll()), + PublicKey: pk.PublicKey, + Dac: mockdac.New(mockdac.WithHistory(h, fixtureHref.String())), + }) + var ( + testfile = "testfile1" + encryptedRef = "a5df670544eaea29e61b19d8739faa4573b19e4426e58a173e51ed0b5e7e2ade" + ) + jsonhttptest.Request(t, client, http.MethodPost, fileUploadResource+"?name="+fileName, http.StatusCreated, + jsonhttptest.WithRequestHeader(api.SwarmActHeader, "true"), + jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr), + jsonhttptest.WithRequestBody(strings.NewReader(testfile)), + jsonhttptest.WithExpectedJSONResponse(api.BzzUploadResponse{ + Reference: swarm.MustParseHexAddress(encryptedRef), + }), + jsonhttptest.WithRequestHeader(api.ContentTypeHeader, "text/html; charset=utf-8"), + jsonhttptest.WithNonEmptyResponseHeader(api.SwarmTagHeader), + jsonhttptest.WithExpectedResponseHeader(api.ETagHeader, fmt.Sprintf("%q", encryptedRef)), + ) + + jsonhttptest.Request(t, client, http.MethodGet, fileDownloadResource(encryptedRef), http.StatusNotFound, + jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{ + Message: "address not found or incorrect", + Code: http.StatusNotFound, + }), + jsonhttptest.WithExpectedResponseHeader(api.ContentTypeHeader, "application/json; charset=utf-8"), + ) + }) + + t.Run("upload-w/o-act-then-download-w/-act", func(t *testing.T) { + client, _, _, _ := newTestServer(t, testServerOptions{ + Storer: storerMock, + Logger: logger, + Post: mockpost.New(mockpost.WithAcceptAll()), + PublicKey: pk.PublicKey, + Dac: mockdac.New(), + }) + var ( + rootHash = "0cb947ccbc410c43139ba4409d83bf89114cb0d79556a651c06c888cf73f4d7e" + sampleHtml = ` + + + +

My First Heading

+ +

My first paragraph.

+ + + ` + ) + + jsonhttptest.Request(t, client, http.MethodPost, fileUploadResource+"?name="+fileName, http.StatusCreated, + jsonhttptest.WithRequestHeader(api.SwarmDeferredUploadHeader, "true"), + jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr), + jsonhttptest.WithRequestBody(strings.NewReader(sampleHtml)), + jsonhttptest.WithExpectedJSONResponse(api.BzzUploadResponse{ + Reference: swarm.MustParseHexAddress(rootHash), + }), + jsonhttptest.WithRequestHeader(api.ContentTypeHeader, "text/html; charset=utf-8"), + jsonhttptest.WithNonEmptyResponseHeader(api.SwarmTagHeader), + jsonhttptest.WithExpectedResponseHeader(api.ETagHeader, fmt.Sprintf("%q", rootHash)), + ) + + jsonhttptest.Request(t, client, http.MethodGet, fileDownloadResource(rootHash), http.StatusInternalServerError, + jsonhttptest.WithRequestHeader(api.SwarmActTimestampHeader, strconv.FormatInt(now, 10)), + jsonhttptest.WithRequestHeader(api.SwarmActHistoryAddressHeader, fixtureHref.String()), + jsonhttptest.WithRequestHeader(api.SwarmActPublisherHeader, publisher), + jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{ + Message: api.ErrActDownload.Error(), + Code: http.StatusInternalServerError, + }), + jsonhttptest.WithExpectedResponseHeader(api.ContentTypeHeader, "application/json; charset=utf-8"), + ) + }) +} + +// nolint:paralleltest,tparallel +// TestDacInvalidPath [negative test]: Expect Bad request when the path address is invalid. +func TestDacInvalidPath(t *testing.T) { + t.Parallel() + var ( + spk, _ = hex.DecodeString("a786dd84b61485de12146fd9c4c02d87e8fd95f0542765cb7fc3d2e428c0bcfa") + pk, _ = crypto.DecodeSecp256k1PrivateKey(spk) + publicKeyBytes = crypto.EncodeSecp256k1PublicKey(&pk.PublicKey) + publisher = hex.EncodeToString(publicKeyBytes) + fileDownloadResource = func(addr string) string { return "/bzz/" + addr } + storerMock = mockstorer.New() + _, fixtureHref = prepareHistoryFixture(storerMock) + logger = log.Noop + now = time.Now().Unix() + ) + + t.Run("invalid-path-params", func(t *testing.T) { + client, _, _, _ := newTestServer(t, testServerOptions{ + Storer: storerMock, + Logger: logger, + Post: mockpost.New(mockpost.WithAcceptAll()), + PublicKey: pk.PublicKey, + Dac: mockdac.New(), + }) + var ( + encryptedRef = "asd" + ) + + jsonhttptest.Request(t, client, http.MethodGet, fileDownloadResource(encryptedRef), http.StatusBadRequest, + jsonhttptest.WithRequestHeader(api.SwarmActTimestampHeader, strconv.FormatInt(now, 10)), + jsonhttptest.WithRequestHeader(api.SwarmActHistoryAddressHeader, fixtureHref.String()), + jsonhttptest.WithRequestHeader(api.SwarmActPublisherHeader, publisher), + jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{ + Code: http.StatusBadRequest, + Message: "invalid path params", + Reasons: []jsonhttp.Reason{ + { + Field: "address", + Error: api.HexInvalidByteError('s').Error(), + }, + }}), + jsonhttptest.WithRequestHeader(api.ContentTypeHeader, "text/html; charset=utf-8"), + ) + }) +} + +// nolint:paralleltest,tparallel +// TestDacHistory tests: +// [positive tests] 1., 2.: uploading a file w/ and w/o history address then downloading it and checking the data. +// [negative test] 3. uploading a file then downloading it with a wrong history address. +// [negative test] 4. uploading a file to a wrong history address. +// [negative test] 4. downloading a file to w/o history address. +func TestDacHistory(t *testing.T) { + t.Parallel() + var ( + spk, _ = hex.DecodeString("a786dd84b61485de12146fd9c4c02d87e8fd95f0542765cb7fc3d2e428c0bcfa") + pk, _ = crypto.DecodeSecp256k1PrivateKey(spk) + publicKeyBytes = crypto.EncodeSecp256k1PublicKey(&pk.PublicKey) + publisher = hex.EncodeToString(publicKeyBytes) + fileUploadResource = "/bzz" + fileDownloadResource = func(addr string) string { return "/bzz/" + addr } + storerMock = mockstorer.New() + h, fixtureHref = prepareHistoryFixture(storerMock) + logger = log.Noop + fileName = "sample.html" + now = time.Now().Unix() + ) + + t.Run("empty-history-upload-then-download-and-check-data", func(t *testing.T) { + client, _, _, _ := newTestServer(t, testServerOptions{ + Storer: storerMock, + Logger: logger, + Post: mockpost.New(mockpost.WithAcceptAll()), + PublicKey: pk.PublicKey, + Dac: mockdac.New(), + }) + var ( + testfile = "testfile1" + encryptedRef = "a5df670544eaea29e61b19d8739faa4573b19e4426e58a173e51ed0b5e7e2ade" + ) + header := jsonhttptest.Request(t, client, http.MethodPost, fileUploadResource+"?name="+fileName, http.StatusCreated, + jsonhttptest.WithRequestHeader(api.SwarmActHeader, "true"), + jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr), + jsonhttptest.WithRequestBody(strings.NewReader(testfile)), + jsonhttptest.WithExpectedJSONResponse(api.BzzUploadResponse{ + Reference: swarm.MustParseHexAddress(encryptedRef), + }), + jsonhttptest.WithRequestHeader(api.ContentTypeHeader, "text/html; charset=utf-8"), + jsonhttptest.WithNonEmptyResponseHeader(api.SwarmTagHeader), + jsonhttptest.WithExpectedResponseHeader(api.ETagHeader, fmt.Sprintf("%q", encryptedRef)), + ) + + historyRef := header.Get(api.SwarmActHistoryAddressHeader) + jsonhttptest.Request(t, client, http.MethodGet, fileDownloadResource(encryptedRef), http.StatusOK, + jsonhttptest.WithRequestHeader(api.SwarmActTimestampHeader, strconv.FormatInt(now, 10)), + jsonhttptest.WithRequestHeader(api.SwarmActHistoryAddressHeader, historyRef), + jsonhttptest.WithRequestHeader(api.SwarmActPublisherHeader, publisher), + jsonhttptest.WithExpectedResponse([]byte(testfile)), + jsonhttptest.WithExpectedContentLength(len(testfile)), + jsonhttptest.WithExpectedResponseHeader(api.ContentTypeHeader, "text/html; charset=utf-8"), + jsonhttptest.WithExpectedResponseHeader(api.ContentDispositionHeader, fmt.Sprintf(`inline; filename="%s"`, fileName)), + ) + }) + + t.Run("with-history-upload-then-download-and-check-data", func(t *testing.T) { + client, _, _, _ := newTestServer(t, testServerOptions{ + Storer: storerMock, + Logger: logger, + Post: mockpost.New(mockpost.WithAcceptAll()), + PublicKey: pk.PublicKey, + Dac: mockdac.New(mockdac.WithHistory(h, fixtureHref.String())), + }) + var ( + encryptedRef = "c611199e1b3674d6bf89a83e518bd16896bf5315109b4a23dcb4682a02d17b97" + testfile = ` + + + +

My First Heading

+ +

My first paragraph.

+ + + ` + ) + + jsonhttptest.Request(t, client, http.MethodPost, fileUploadResource+"?name="+fileName, http.StatusCreated, + jsonhttptest.WithRequestHeader(api.SwarmActHeader, "true"), + jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr), + jsonhttptest.WithRequestHeader(api.SwarmActHistoryAddressHeader, fixtureHref.String()), + jsonhttptest.WithRequestBody(strings.NewReader(testfile)), + jsonhttptest.WithExpectedJSONResponse(api.BzzUploadResponse{ + Reference: swarm.MustParseHexAddress(encryptedRef), + }), + jsonhttptest.WithRequestHeader(api.ContentTypeHeader, "text/html; charset=utf-8"), + jsonhttptest.WithNonEmptyResponseHeader(api.SwarmTagHeader), + jsonhttptest.WithExpectedResponseHeader(api.ETagHeader, fmt.Sprintf("%q", encryptedRef)), + ) + + jsonhttptest.Request(t, client, http.MethodGet, fileDownloadResource(encryptedRef), http.StatusOK, + jsonhttptest.WithRequestHeader(api.SwarmActTimestampHeader, strconv.FormatInt(now, 10)), + jsonhttptest.WithRequestHeader(api.SwarmActHistoryAddressHeader, fixtureHref.String()), + jsonhttptest.WithRequestHeader(api.SwarmActPublisherHeader, publisher), + jsonhttptest.WithExpectedResponse([]byte(testfile)), + jsonhttptest.WithExpectedContentLength(len(testfile)), + jsonhttptest.WithExpectedResponseHeader(api.ContentTypeHeader, "text/html; charset=utf-8"), + jsonhttptest.WithExpectedResponseHeader(api.ContentDispositionHeader, fmt.Sprintf(`inline; filename="%s"`, fileName)), + ) + }) + + t.Run("upload-then-download-wrong-history", func(t *testing.T) { + client, _, _, _ := newTestServer(t, testServerOptions{ + Storer: storerMock, + Logger: logger, + Post: mockpost.New(mockpost.WithAcceptAll()), + PublicKey: pk.PublicKey, + Dac: mockdac.New(mockdac.WithHistory(h, fixtureHref.String())), + }) + var ( + testfile = "testfile1" + encryptedRef = "a5df670544eaea29e61b19d8739faa4573b19e4426e58a173e51ed0b5e7e2ade" + ) + jsonhttptest.Request(t, client, http.MethodPost, fileUploadResource+"?name="+fileName, http.StatusCreated, + jsonhttptest.WithRequestHeader(api.SwarmActHeader, "true"), + jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr), + jsonhttptest.WithRequestBody(strings.NewReader(testfile)), + jsonhttptest.WithExpectedJSONResponse(api.BzzUploadResponse{ + Reference: swarm.MustParseHexAddress(encryptedRef), + }), + jsonhttptest.WithRequestHeader(api.ContentTypeHeader, "text/html; charset=utf-8"), + jsonhttptest.WithNonEmptyResponseHeader(api.SwarmTagHeader), + jsonhttptest.WithExpectedResponseHeader(api.ETagHeader, fmt.Sprintf("%q", encryptedRef)), + ) + + jsonhttptest.Request(t, client, http.MethodGet, fileDownloadResource(encryptedRef), http.StatusInternalServerError, + jsonhttptest.WithRequestHeader(api.SwarmActTimestampHeader, strconv.FormatInt(now, 10)), + jsonhttptest.WithRequestHeader(api.SwarmActHistoryAddressHeader, "fc4e9fe978991257b897d987bc4ff13058b66ef45a53189a0b4fe84bb3346396"), + jsonhttptest.WithRequestHeader(api.SwarmActPublisherHeader, publisher), + jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{ + Message: api.ErrActDownload.Error(), + Code: http.StatusInternalServerError, + }), + jsonhttptest.WithExpectedResponseHeader(api.ContentTypeHeader, "application/json; charset=utf-8"), + ) + }) + + t.Run("upload-wrong-history", func(t *testing.T) { + client, _, _, _ := newTestServer(t, testServerOptions{ + Storer: storerMock, + Logger: logger, + Post: mockpost.New(mockpost.WithAcceptAll()), + PublicKey: pk.PublicKey, + Dac: mockdac.New(), + }) + var ( + testfile = "testfile1" + ) + + jsonhttptest.Request(t, client, http.MethodPost, fileUploadResource+"?name="+fileName, http.StatusInternalServerError, + jsonhttptest.WithRequestHeader(api.SwarmActHeader, "true"), + jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr), + jsonhttptest.WithRequestHeader(api.SwarmActHistoryAddressHeader, fixtureHref.String()), + jsonhttptest.WithRequestBody(strings.NewReader(testfile)), + jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{ + Message: api.ErrActUpload.Error(), + Code: http.StatusInternalServerError, + }), + jsonhttptest.WithRequestHeader(api.ContentTypeHeader, "text/html; charset=utf-8"), + ) + }) + + t.Run("download-w/o-history", func(t *testing.T) { + client, _, _, _ := newTestServer(t, testServerOptions{ + Storer: storerMock, + Logger: logger, + Post: mockpost.New(mockpost.WithAcceptAll()), + PublicKey: pk.PublicKey, + Dac: mockdac.New(mockdac.WithHistory(h, fixtureHref.String())), + }) + var ( + encryptedRef = "a5df670544eaea29e61b19d8739faa4573b19e4426e58a173e51ed0b5e7e2ade" + ) + + jsonhttptest.Request(t, client, http.MethodGet, fileDownloadResource(encryptedRef), http.StatusNotFound, + jsonhttptest.WithRequestHeader(api.SwarmActTimestampHeader, strconv.FormatInt(now, 10)), + jsonhttptest.WithRequestHeader(api.SwarmActPublisherHeader, publisher), + jsonhttptest.WithExpectedResponseHeader(api.ContentTypeHeader, "application/json; charset=utf-8"), + ) + }) +} + +// nolint:paralleltest,tparallel +// TestDacTimestamp doc. comment +// [positive test] 1.: uploading a file w/ ACT then download it w/ timestamp and check the data. +// [negative test] 2.: try to download a file w/o timestamp. +func TestDacTimestamp(t *testing.T) { + t.Parallel() + var ( + spk, _ = hex.DecodeString("a786dd84b61485de12146fd9c4c02d87e8fd95f0542765cb7fc3d2e428c0bcfa") + pk, _ = crypto.DecodeSecp256k1PrivateKey(spk) + publicKeyBytes = crypto.EncodeSecp256k1PublicKey(&pk.PublicKey) + publisher = hex.EncodeToString(publicKeyBytes) + fileUploadResource = "/bzz" + fileDownloadResource = func(addr string) string { return "/bzz/" + addr } + storerMock = mockstorer.New() + h, fixtureHref = prepareHistoryFixture(storerMock) + logger = log.Noop + fileName = "sample.html" + ) + t.Run("upload-then-download-with-timestamp-and-check-data", func(t *testing.T) { + client, _, _, _ := newTestServer(t, testServerOptions{ + Storer: storerMock, + Logger: logger, + Post: mockpost.New(mockpost.WithAcceptAll()), + PublicKey: pk.PublicKey, + Dac: mockdac.New(mockdac.WithHistory(h, fixtureHref.String())), + }) + var ( + thirdTime = time.Date(2015, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() + encryptedRef = "c611199e1b3674d6bf89a83e518bd16896bf5315109b4a23dcb4682a02d17b97" + testfile = ` + + + +

My First Heading

+ +

My first paragraph.

+ + + ` + ) + + jsonhttptest.Request(t, client, http.MethodPost, fileUploadResource+"?name="+fileName, http.StatusCreated, + jsonhttptest.WithRequestHeader(api.SwarmActHeader, "true"), + jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr), + jsonhttptest.WithRequestHeader(api.SwarmActHistoryAddressHeader, fixtureHref.String()), + jsonhttptest.WithRequestBody(strings.NewReader(testfile)), + jsonhttptest.WithExpectedJSONResponse(api.BzzUploadResponse{ + Reference: swarm.MustParseHexAddress(encryptedRef), + }), + jsonhttptest.WithRequestHeader(api.ContentTypeHeader, "text/html; charset=utf-8"), + jsonhttptest.WithNonEmptyResponseHeader(api.SwarmTagHeader), + jsonhttptest.WithExpectedResponseHeader(api.ETagHeader, fmt.Sprintf("%q", encryptedRef)), + ) + + jsonhttptest.Request(t, client, http.MethodGet, fileDownloadResource(encryptedRef), http.StatusOK, + jsonhttptest.WithRequestHeader(api.SwarmActTimestampHeader, strconv.FormatInt(thirdTime, 10)), + jsonhttptest.WithRequestHeader(api.SwarmActHistoryAddressHeader, fixtureHref.String()), + jsonhttptest.WithRequestHeader(api.SwarmActPublisherHeader, publisher), + jsonhttptest.WithExpectedResponse([]byte(testfile)), + jsonhttptest.WithExpectedContentLength(len(testfile)), + jsonhttptest.WithExpectedResponseHeader(api.ContentTypeHeader, "text/html; charset=utf-8"), + jsonhttptest.WithExpectedResponseHeader(api.ContentDispositionHeader, fmt.Sprintf(`inline; filename="%s"`, fileName)), + ) + }) + + t.Run("download-w/o-timestamp", func(t *testing.T) { + var ( + encryptedRef = "a5df670544eaea29e61b19d8739faa4573b19e4426e58a173e51ed0b5e7e2ade" + ) + client, _, _, _ := newTestServer(t, testServerOptions{ + Storer: storerMock, + Logger: logger, + Post: mockpost.New(mockpost.WithAcceptAll()), + PublicKey: pk.PublicKey, + Dac: mockdac.New(mockdac.WithHistory(h, fixtureHref.String())), + }) + + jsonhttptest.Request(t, client, http.MethodGet, fileDownloadResource(encryptedRef), http.StatusNotFound, + jsonhttptest.WithRequestHeader(api.SwarmActHistoryAddressHeader, fixtureHref.String()), + jsonhttptest.WithRequestHeader(api.SwarmActPublisherHeader, publisher), + jsonhttptest.WithExpectedResponseHeader(api.ContentTypeHeader, "application/json; charset=utf-8"), + ) + }) +} + +// nolint:paralleltest,tparallel +// TestDacPublisher doc. comment +// [positive test] 1.: uploading a file w/ ACT then download it w/ the publisher address and check the data. +// [negative test] 2.: expect Bad request when the public key is invalid. +// [negative test] 3.: try to download a file w/ an incorrect publisher address. +// [negative test] 3.: try to download a file w/o a publisher address. +func TestDacPublisher(t *testing.T) { + t.Parallel() + var ( + spk, _ = hex.DecodeString("a786dd84b61485de12146fd9c4c02d87e8fd95f0542765cb7fc3d2e428c0bcfa") + pk, _ = crypto.DecodeSecp256k1PrivateKey(spk) + publicKeyBytes = crypto.EncodeSecp256k1PublicKey(&pk.PublicKey) + publisher = hex.EncodeToString(publicKeyBytes) + fileUploadResource = "/bzz" + fileDownloadResource = func(addr string) string { return "/bzz/" + addr } + storerMock = mockstorer.New() + h, fixtureHref = prepareHistoryFixture(storerMock) + logger = log.Noop + fileName = "sample.html" + now = time.Now().Unix() + ) + + t.Run("upload-then-download-w/-publisher-and-check-data", func(t *testing.T) { + client, _, _, _ := newTestServer(t, testServerOptions{ + Storer: storerMock, + Logger: logger, + Post: mockpost.New(mockpost.WithAcceptAll()), + PublicKey: pk.PublicKey, + Dac: mockdac.New(mockdac.WithHistory(h, fixtureHref.String()), mockdac.WithPublisher(publisher)), + }) + var ( + encryptedRef = "a5a26b4915d7ce1622f9ca52252092cf2445f98d359dabaf52588c05911aaf4f" + testfile = ` + + + +

My First Heading

+ +

My first paragraph.

+ + + ` + ) + + jsonhttptest.Request(t, client, http.MethodPost, fileUploadResource+"?name="+fileName, http.StatusCreated, + jsonhttptest.WithRequestHeader(api.SwarmActHeader, "true"), + jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr), + jsonhttptest.WithRequestHeader(api.SwarmActHistoryAddressHeader, fixtureHref.String()), + jsonhttptest.WithRequestBody(strings.NewReader(testfile)), + jsonhttptest.WithExpectedJSONResponse(api.BzzUploadResponse{ + Reference: swarm.MustParseHexAddress(encryptedRef), + }), + jsonhttptest.WithRequestHeader(api.ContentTypeHeader, "text/html; charset=utf-8"), + jsonhttptest.WithNonEmptyResponseHeader(api.SwarmTagHeader), + jsonhttptest.WithExpectedResponseHeader(api.ETagHeader, fmt.Sprintf("%q", encryptedRef)), + ) + + jsonhttptest.Request(t, client, http.MethodGet, fileDownloadResource(encryptedRef), http.StatusOK, + jsonhttptest.WithRequestHeader(api.SwarmActTimestampHeader, strconv.FormatInt(now, 10)), + jsonhttptest.WithRequestHeader(api.SwarmActHistoryAddressHeader, fixtureHref.String()), + jsonhttptest.WithRequestHeader(api.SwarmActPublisherHeader, publisher), + jsonhttptest.WithExpectedResponse([]byte(testfile)), + jsonhttptest.WithExpectedContentLength(len(testfile)), + jsonhttptest.WithExpectedResponseHeader(api.ContentTypeHeader, "text/html; charset=utf-8"), + jsonhttptest.WithExpectedResponseHeader(api.ContentDispositionHeader, fmt.Sprintf(`inline; filename="%s"`, fileName)), + ) + }) + + t.Run("upload-then-download-invalid-publickey", func(t *testing.T) { + client, _, _, _ := newTestServer(t, testServerOptions{ + Storer: storerMock, + Logger: logger, + Post: mockpost.New(mockpost.WithAcceptAll()), + PublicKey: pk.PublicKey, + Dac: mockdac.New(mockdac.WithPublisher(publisher)), + }) + var ( + publickey = "b786dd84b61485de12146fd9c4c02d87e8fd95f0542765cb7fc3d2e428c0bcfb" + encryptedRef = "a5a26b4915d7ce1622f9ca52252092cf2445f98d359dabaf52588c05911aaf4f" + testfile = ` + + + +

My First Heading

+ +

My first paragraph.

+ + + ` + ) + + header := jsonhttptest.Request(t, client, http.MethodPost, fileUploadResource+"?name="+fileName, http.StatusCreated, + jsonhttptest.WithRequestHeader(api.SwarmActHeader, "true"), + jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr), + jsonhttptest.WithRequestBody(strings.NewReader(testfile)), + jsonhttptest.WithExpectedJSONResponse(api.BzzUploadResponse{ + Reference: swarm.MustParseHexAddress(encryptedRef), + }), + jsonhttptest.WithRequestHeader(api.ContentTypeHeader, "text/html; charset=utf-8"), + jsonhttptest.WithNonEmptyResponseHeader(api.SwarmTagHeader), + jsonhttptest.WithExpectedResponseHeader(api.ETagHeader, fmt.Sprintf("%q", encryptedRef)), + ) + + historyRef := header.Get(api.SwarmActHistoryAddressHeader) + jsonhttptest.Request(t, client, http.MethodGet, fileDownloadResource(encryptedRef), http.StatusBadRequest, + jsonhttptest.WithRequestHeader(api.SwarmActTimestampHeader, strconv.FormatInt(now, 10)), + jsonhttptest.WithRequestHeader(api.SwarmActHistoryAddressHeader, historyRef), + jsonhttptest.WithRequestHeader(api.SwarmActPublisherHeader, publickey), + jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{ + Code: http.StatusBadRequest, + Message: "invalid header params", + Reasons: []jsonhttp.Reason{ + { + Field: "Swarm-Act-Publisher", + Error: "malformed public key: invalid length: 32", + }, + }}), + jsonhttptest.WithRequestHeader(api.ContentTypeHeader, "text/html; charset=utf-8"), + ) + }) + + t.Run("download-w/-wrong-publisher", func(t *testing.T) { + var ( + downloader = "03c712a7e29bc792ac8d8ae49793d28d5bda27ed70f0d90697b2fb456c0a168bd2" + encryptedRef = "a5df670544eaea29e61b19d8739faa4573b19e4426e58a173e51ed0b5e7e2ade" + ) + client, _, _, _ := newTestServer(t, testServerOptions{ + Storer: storerMock, + Logger: logger, + Post: mockpost.New(mockpost.WithAcceptAll()), + PublicKey: pk.PublicKey, + Dac: mockdac.New(mockdac.WithHistory(h, fixtureHref.String()), mockdac.WithPublisher(publisher)), + }) + + jsonhttptest.Request(t, client, http.MethodGet, fileDownloadResource(encryptedRef), http.StatusInternalServerError, + jsonhttptest.WithRequestHeader(api.SwarmActTimestampHeader, strconv.FormatInt(now, 10)), + jsonhttptest.WithRequestHeader(api.SwarmActHistoryAddressHeader, fixtureHref.String()), + jsonhttptest.WithRequestHeader(api.SwarmActPublisherHeader, downloader), + jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{ + Message: api.ErrActDownload.Error(), + Code: http.StatusInternalServerError, + }), + jsonhttptest.WithExpectedResponseHeader(api.ContentTypeHeader, "application/json; charset=utf-8"), + ) + }) + + t.Run("download-w/o-publisher", func(t *testing.T) { + var ( + encryptedRef = "a5df670544eaea29e61b19d8739faa4573b19e4426e58a173e51ed0b5e7e2ade" + ) + client, _, _, _ := newTestServer(t, testServerOptions{ + Storer: storerMock, + Logger: logger, + Post: mockpost.New(mockpost.WithAcceptAll()), + PublicKey: pk.PublicKey, + Dac: mockdac.New(mockdac.WithHistory(h, fixtureHref.String()), mockdac.WithPublisher(publisher)), + }) + + jsonhttptest.Request(t, client, http.MethodGet, fileDownloadResource(encryptedRef), http.StatusNotFound, + jsonhttptest.WithRequestHeader(api.SwarmActTimestampHeader, strconv.FormatInt(now, 10)), + jsonhttptest.WithRequestHeader(api.SwarmActHistoryAddressHeader, fixtureHref.String()), + jsonhttptest.WithRequestHeader(api.SwarmActPublisherHeader, publisher), + jsonhttptest.WithExpectedResponseHeader(api.ContentTypeHeader, "application/json; charset=utf-8"), + ) + }) +} diff --git a/pkg/api/export_test.go b/pkg/api/export_test.go index 812ce522ba0..fd78ae6974f 100644 --- a/pkg/api/export_test.go +++ b/pkg/api/export_test.go @@ -38,6 +38,8 @@ var ( ErrInvalidNameOrAddress = errInvalidNameOrAddress ErrUnsupportedDevNodeOperation = errUnsupportedDevNodeOperation ErrOperationSupportedOnlyInFullMode = errOperationSupportedOnlyInFullMode + ErrActDownload = errActDownload + ErrActUpload = errActUpload ) var ( diff --git a/pkg/api/feed.go b/pkg/api/feed.go index 09fdf6515ec..eed03febe31 100644 --- a/pkg/api/feed.go +++ b/pkg/api/feed.go @@ -137,9 +137,11 @@ func (s *Service) feedPostHandler(w http.ResponseWriter, r *http.Request) { } headers := struct { - BatchID []byte `map:"Swarm-Postage-Batch-Id" validate:"required"` - Pin bool `map:"Swarm-Pin"` - Deferred *bool `map:"Swarm-Deferred-Upload"` + BatchID []byte `map:"Swarm-Postage-Batch-Id" validate:"required"` + Pin bool `map:"Swarm-Pin"` + Deferred *bool `map:"Swarm-Deferred-Upload"` + Act bool `map:"Swarm-Act"` + HistoryAddress *swarm.Address `map:"Swarm-Act-History-Address"` }{} if response := s.mapStructure(r.Header, &headers); response != nil { response("invalid header params", logger, w) @@ -244,6 +246,15 @@ func (s *Service) feedPostHandler(w http.ResponseWriter, r *http.Request) { } return } + // TODO: do we want to allow feed act upload/ download? + encryptedReference := ref + if headers.Act { + encryptedReference, err = s.actEncryptionHandler(r.Context(), logger, w, putter, ref, headers.HistoryAddress) + if err != nil { + jsonhttp.InternalServerError(w, errActUpload) + return + } + } err = putter.Done(ref) if err != nil { @@ -253,7 +264,7 @@ func (s *Service) feedPostHandler(w http.ResponseWriter, r *http.Request) { return } - jsonhttp.Created(w, feedReferenceResponse{Reference: ref}) + jsonhttp.Created(w, feedReferenceResponse{Reference: encryptedReference}) } func parseFeedUpdate(ch swarm.Chunk) (swarm.Address, int64, error) { diff --git a/pkg/api/router.go b/pkg/api/router.go index c0c6d02782c..0e9ed177377 100644 --- a/pkg/api/router.go +++ b/pkg/api/router.go @@ -214,10 +214,12 @@ func (s *Service) mountAPI() { "GET": web.ChainHandlers( s.contentLengthMetricMiddleware(), s.newTracingHandler("bytes-download"), + s.actDecryptionHandler(), web.FinalHandlerFunc(s.bytesGetHandler), ), "HEAD": web.ChainHandlers( s.newTracingHandler("bytes-head"), + s.actDecryptionHandler(), web.FinalHandlerFunc(s.bytesHeadHandler), ), }) @@ -235,8 +237,14 @@ func (s *Service) mountAPI() { )) handle("/chunks/{address}", jsonhttp.MethodHandler{ - "GET": http.HandlerFunc(s.chunkGetHandler), - "HEAD": http.HandlerFunc(s.hasChunkHandler), + "GET": web.ChainHandlers( + s.actDecryptionHandler(), + web.FinalHandlerFunc(s.chunkGetHandler), + ), + "HEAD": web.ChainHandlers( + s.actDecryptionHandler(), + web.FinalHandlerFunc(s.hasChunkHandler), + ), }) handle("/soc/{owner}/{id}", jsonhttp.MethodHandler{ @@ -272,9 +280,11 @@ func (s *Service) mountAPI() { "GET": web.ChainHandlers( s.contentLengthMetricMiddleware(), s.newTracingHandler("bzz-download"), + s.actDecryptionHandler(), web.FinalHandlerFunc(s.bzzDownloadHandler), ), "HEAD": web.ChainHandlers( + s.actDecryptionHandler(), web.FinalHandlerFunc(s.bzzHeadHandler), ), }) @@ -414,7 +424,10 @@ func (s *Service) mountBusinessDebug() { }) handle("/chunks/{address}", jsonhttp.MethodHandler{ - "GET": http.HandlerFunc(s.hasChunkHandler), + "GET": web.ChainHandlers( + s.actDecryptionHandler(), + web.FinalHandlerFunc(s.hasChunkHandler), + ), }) handle("/topology", jsonhttp.MethodHandler{ diff --git a/pkg/api/soc.go b/pkg/api/soc.go index 0abf338deb9..b4ce98e91eb 100644 --- a/pkg/api/soc.go +++ b/pkg/api/soc.go @@ -43,8 +43,10 @@ func (s *Service) socUploadHandler(w http.ResponseWriter, r *http.Request) { } headers := struct { - BatchID []byte `map:"Swarm-Postage-Batch-Id" validate:"required"` - Pin bool `map:"Swarm-Pin"` + BatchID []byte `map:"Swarm-Postage-Batch-Id" validate:"required"` + Pin bool `map:"Swarm-Pin"` + Act bool `map:"Swarm-Act"` + HistoryAddress *swarm.Address `map:"Swarm-Act-History-Address"` }{} if response := s.mapStructure(r.Header, &headers); response != nil { response("invalid header params", logger, w) @@ -155,6 +157,15 @@ func (s *Service) socUploadHandler(w http.ResponseWriter, r *http.Request) { return } + encryptedReference := sch.Address() + if headers.Act { + encryptedReference, err = s.actEncryptionHandler(r.Context(), logger, w, putter, sch.Address(), headers.HistoryAddress) + if err != nil { + jsonhttp.InternalServerError(w, errActUpload) + return + } + } + err = putter.Put(r.Context(), sch) if err != nil { logger.Debug("write chunk failed", "chunk_address", sch.Address(), "error", err) @@ -171,5 +182,5 @@ func (s *Service) socUploadHandler(w http.ResponseWriter, r *http.Request) { return } - jsonhttp.Created(w, chunkAddressResponse{Reference: sch.Address()}) + jsonhttp.Created(w, socPostResponse{Reference: encryptedReference}) } diff --git a/pkg/dynamicaccess/accesslogic.go b/pkg/dynamicaccess/accesslogic.go index a2a6fbdce24..be9e8b194f1 100644 --- a/pkg/dynamicaccess/accesslogic.go +++ b/pkg/dynamicaccess/accesslogic.go @@ -1,6 +1,7 @@ package dynamicaccess import ( + "context" "crypto/ecdsa" encryption "github.com/ethersphere/bee/v2/pkg/encryption" @@ -14,7 +15,7 @@ var hashFunc = sha3.NewLegacyKeccak256 // Read-only interface for the ACT type Decryptor interface { // DecryptRef will return a decrypted reference, for given encrypted reference and grantee - DecryptRef(storage kvs.KeyValueStore, encryptedRef swarm.Address, publisher *ecdsa.PublicKey) (swarm.Address, error) + DecryptRef(ctx context.Context, storage kvs.KeyValueStore, encryptedRef swarm.Address, publisher *ecdsa.PublicKey) (swarm.Address, error) // Embedding the Session interface Session } @@ -24,9 +25,9 @@ type Control interface { // Embedding the Decryptor interface Decryptor // Adds a new grantee to the ACT - AddGrantee(storage kvs.KeyValueStore, publisherPubKey, granteePubKey *ecdsa.PublicKey, accessKey *encryption.Key) error + AddGrantee(ctx context.Context, storage kvs.KeyValueStore, publisherPubKey, granteePubKey *ecdsa.PublicKey, accessKey *encryption.Key) error // Encrypts a Swarm reference for a given grantee - EncryptRef(storage kvs.KeyValueStore, grantee *ecdsa.PublicKey, ref swarm.Address) (swarm.Address, error) + EncryptRef(ctx context.Context, storage kvs.KeyValueStore, grantee *ecdsa.PublicKey, ref swarm.Address) (swarm.Address, error) } type ActLogic struct { @@ -36,17 +37,17 @@ type ActLogic struct { var _ Control = (*ActLogic)(nil) // Adds a new publisher to an empty act -func (al ActLogic) AddPublisher(storage kvs.KeyValueStore, publisher *ecdsa.PublicKey) error { +func (al ActLogic) AddPublisher(ctx context.Context, storage kvs.KeyValueStore, publisher *ecdsa.PublicKey) error { accessKey := encryption.GenerateRandomKey(encryption.KeyLength) - return al.AddGrantee(storage, publisher, publisher, &accessKey) + return al.AddGrantee(ctx, storage, publisher, publisher, &accessKey) } // Encrypts a SWARM reference for a publisher -func (al ActLogic) EncryptRef(storage kvs.KeyValueStore, publisherPubKey *ecdsa.PublicKey, ref swarm.Address) (swarm.Address, error) { - accessKey, err := al.getAccessKey(storage, publisherPubKey) +func (al ActLogic) EncryptRef(ctx context.Context, storage kvs.KeyValueStore, publisherPubKey *ecdsa.PublicKey, ref swarm.Address) (swarm.Address, error) { + accessKey, err := al.getAccessKey(ctx, storage, publisherPubKey) if err != nil { - return swarm.EmptyAddress, err + return swarm.ZeroAddress, err } refCipher := encryption.New(accessKey, 0, uint32(0), hashFunc) encryptedRef, _ := refCipher.Encrypt(ref.Bytes()) @@ -55,13 +56,13 @@ func (al ActLogic) EncryptRef(storage kvs.KeyValueStore, publisherPubKey *ecdsa. } // Adds a new grantee to the ACT -func (al ActLogic) AddGrantee(storage kvs.KeyValueStore, publisherPubKey, granteePubKey *ecdsa.PublicKey, accessKeyPointer *encryption.Key) error { +func (al ActLogic) AddGrantee(ctx context.Context, storage kvs.KeyValueStore, publisherPubKey, granteePubKey *ecdsa.PublicKey, accessKeyPointer *encryption.Key) error { var accessKey encryption.Key var err error // Declare the "err" variable if accessKeyPointer == nil { // Get previously generated access key - accessKey, err = al.getAccessKey(storage, publisherPubKey) + accessKey, err = al.getAccessKey(ctx, storage, publisherPubKey) if err != nil { return err } @@ -87,11 +88,11 @@ func (al ActLogic) AddGrantee(storage kvs.KeyValueStore, publisherPubKey, grante } // Add the new encrypted access key for the Act - return storage.Put(lookupKey, granteeEncryptedAccessKey) + return storage.Put(ctx, lookupKey, granteeEncryptedAccessKey) } // Will return the access key for a publisher (public key) -func (al *ActLogic) getAccessKey(storage kvs.KeyValueStore, publisherPubKey *ecdsa.PublicKey) ([]byte, error) { +func (al *ActLogic) getAccessKey(ctx context.Context, storage kvs.KeyValueStore, publisherPubKey *ecdsa.PublicKey) ([]byte, error) { keys, err := al.getKeys(publisherPubKey) if err != nil { return nil, err @@ -100,13 +101,12 @@ func (al *ActLogic) getAccessKey(storage kvs.KeyValueStore, publisherPubKey *ecd publisherAKDecryptionKey := keys[1] // no need to constructor call if value not found in act accessKeyDecryptionCipher := encryption.New(encryption.Key(publisherAKDecryptionKey), 0, uint32(0), hashFunc) - encryptedAK, err := storage.Get(publisherLookupKey) + encryptedAK, err := storage.Get(ctx, publisherLookupKey) if err != nil { return nil, err } return accessKeyDecryptionCipher.Decrypt(encryptedAK) - } var oneByteArray = []byte{1} @@ -118,32 +118,32 @@ func (al *ActLogic) getKeys(publicKey *ecdsa.PublicKey) ([][]byte, error) { } // DecryptRef will return a decrypted reference, for given encrypted reference and publisher -func (al ActLogic) DecryptRef(storage kvs.KeyValueStore, encryptedRef swarm.Address, publisher *ecdsa.PublicKey) (swarm.Address, error) { +func (al ActLogic) DecryptRef(ctx context.Context, storage kvs.KeyValueStore, encryptedRef swarm.Address, publisher *ecdsa.PublicKey) (swarm.Address, error) { keys, err := al.getKeys(publisher) if err != nil { - return swarm.EmptyAddress, err + return swarm.ZeroAddress, err } lookupKey := keys[0] accessKeyDecryptionKey := keys[1] // Lookup encrypted access key from the ACT manifest - encryptedAccessKey, err := storage.Get(lookupKey) + encryptedAccessKey, err := storage.Get(ctx, lookupKey) if err != nil { - return swarm.EmptyAddress, err + return swarm.ZeroAddress, err } // Decrypt access key accessKeyCipher := encryption.New(encryption.Key(accessKeyDecryptionKey), 0, uint32(0), hashFunc) accessKey, err := accessKeyCipher.Decrypt(encryptedAccessKey) if err != nil { - return swarm.EmptyAddress, err + return swarm.ZeroAddress, err } // Decrypt reference refCipher := encryption.New(accessKey, 0, uint32(0), hashFunc) ref, err := refCipher.Decrypt(encryptedRef.Bytes()) if err != nil { - return swarm.EmptyAddress, err + return swarm.ZeroAddress, err } return swarm.NewAddress(ref), nil diff --git a/pkg/dynamicaccess/accesslogic_test.go b/pkg/dynamicaccess/accesslogic_test.go index be115f424d1..66a3f8930e3 100644 --- a/pkg/dynamicaccess/accesslogic_test.go +++ b/pkg/dynamicaccess/accesslogic_test.go @@ -1,6 +1,7 @@ package dynamicaccess_test import ( + "context" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" @@ -14,7 +15,7 @@ import ( ) // Generates a new test environment with a fix private key -func setupAccessLogic2() dynamicaccess.ActLogic { +func setupAccessLogic() dynamicaccess.ActLogic { privateKey := getPrivKey(1) diffieHellman := dynamicaccess.NewDefaultSession(privateKey) al := dynamicaccess.NewLogic(diffieHellman) @@ -50,10 +51,11 @@ func getPrivKey(keyNumber int) *ecdsa.PrivateKey { } func TestDecryptRef_Success(t *testing.T) { + ctx := context.Background() id0 := getPrivKey(0) s := kvsmock.New() - al := setupAccessLogic2() - err := al.AddPublisher(s, &id0.PublicKey) + al := setupAccessLogic() + err := al.AddPublisher(ctx, s, &id0.PublicKey) if err != nil { t.Errorf("AddPublisher: expected no error, got %v", err) } @@ -63,14 +65,14 @@ func TestDecryptRef_Success(t *testing.T) { expectedRef := swarm.NewAddress(byteRef) t.Logf("encryptedRef: %s", expectedRef.String()) - encryptedRef, err := al.EncryptRef(s, &id0.PublicKey, expectedRef) + encryptedRef, err := al.EncryptRef(ctx, s, &id0.PublicKey, expectedRef) t.Logf("encryptedRef: %s", encryptedRef.String()) if err != nil { t.Errorf("There was an error while calling EncryptRef: ") t.Error(err) } - acutalRef, err := al.DecryptRef(s, encryptedRef, &id0.PublicKey) + acutalRef, err := al.DecryptRef(ctx, s, encryptedRef, &id0.PublicKey) if err != nil { t.Errorf("There was an error while calling Get: ") t.Error(err) @@ -83,18 +85,19 @@ func TestDecryptRef_Success(t *testing.T) { } func TestDecryptRefWithGrantee_Success(t *testing.T) { + ctx := context.Background() id0, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) diffieHellman := dynamicaccess.NewDefaultSession(id0) al := dynamicaccess.NewLogic(diffieHellman) s := kvsmock.New() - err := al.AddPublisher(s, &id0.PublicKey) + err := al.AddPublisher(ctx, s, &id0.PublicKey) if err != nil { t.Errorf("AddPublisher: expected no error, got %v", err) } id1, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - err = al.AddGrantee(s, &id0.PublicKey, &id1.PublicKey, nil) + err = al.AddGrantee(ctx, s, &id0.PublicKey, &id1.PublicKey, nil) if err != nil { t.Errorf("AddNewGrantee: expected no error, got %v", err) } @@ -104,7 +107,7 @@ func TestDecryptRefWithGrantee_Success(t *testing.T) { expectedRef := swarm.NewAddress(byteRef) t.Logf("encryptedRef: %s", expectedRef.String()) - encryptedRef, err := al.EncryptRef(s, &id0.PublicKey, expectedRef) + encryptedRef, err := al.EncryptRef(ctx, s, &id0.PublicKey, expectedRef) t.Logf("encryptedRef: %s", encryptedRef.String()) if err != nil { t.Errorf("There was an error while calling EncryptRef: ") @@ -113,7 +116,7 @@ func TestDecryptRefWithGrantee_Success(t *testing.T) { diffieHellman2 := dynamicaccess.NewDefaultSession(id1) granteeAccessLogic := dynamicaccess.NewLogic(diffieHellman2) - acutalRef, err := granteeAccessLogic.DecryptRef(s, encryptedRef, &id0.PublicKey) + acutalRef, err := granteeAccessLogic.DecryptRef(ctx, s, encryptedRef, &id0.PublicKey) if err != nil { t.Errorf("There was an error while calling Get: ") t.Error(err) @@ -128,18 +131,19 @@ func TestDecryptRefWithGrantee_Success(t *testing.T) { func TestDecryptRef_Error(t *testing.T) { id0 := getPrivKey(0) + ctx := context.Background() s := kvsmock.New() - al := setupAccessLogic2() - err := al.AddPublisher(s, &id0.PublicKey) + al := setupAccessLogic() + err := al.AddPublisher(ctx, s, &id0.PublicKey) if err != nil { t.Errorf("AddPublisher: expected no error, got %v", err) } expectedRef := "39a5ea87b141fe44aa609c3327ecd896c0e2122897f5f4bbacf74db1033c5559" - encryptedRef, _ := al.EncryptRef(s, &id0.PublicKey, swarm.NewAddress([]byte(expectedRef))) + encryptedRef, _ := al.EncryptRef(ctx, s, &id0.PublicKey, swarm.NewAddress([]byte(expectedRef))) - r, err := al.DecryptRef(s, encryptedRef, nil) + r, err := al.DecryptRef(ctx, s, encryptedRef, nil) if err == nil { t.Logf("r: %s", r.String()) t.Errorf("Get should return encrypted access key not found error!") @@ -150,9 +154,10 @@ func TestAddPublisher(t *testing.T) { id0 := getPrivKey(0) savedLookupKey := "b6ee086390c280eeb9824c331a4427596f0c8510d5564bc1b6168d0059a46e2b" s := kvsmock.New() + ctx := context.Background() - al := setupAccessLogic2() - err := al.AddPublisher(s, &id0.PublicKey) + al := setupAccessLogic() + err := al.AddPublisher(ctx, s, &id0.PublicKey) if err != nil { t.Errorf("AddPublisher: expected no error, got %v", err) } @@ -162,7 +167,7 @@ func TestAddPublisher(t *testing.T) { t.Errorf("DecodeString: expected no error, got %v", err) } - encryptedAccessKey, err := s.Get(decodedSavedLookupKey) + encryptedAccessKey, err := s.Get(ctx, decodedSavedLookupKey) if err != nil { t.Errorf("Lookup: expected no error, got %v", err) } @@ -183,24 +188,25 @@ func TestAddNewGranteeToContent(t *testing.T) { id0 := getPrivKey(0) id1 := getPrivKey(1) id2 := getPrivKey(2) + ctx := context.Background() publisherLookupKey := "b6ee086390c280eeb9824c331a4427596f0c8510d5564bc1b6168d0059a46e2b" firstAddedGranteeLookupKey := "a13678e81f9d939b9401a3ad7e548d2ceb81c50f8c76424296e83a1ad79c0df0" secondAddedGranteeLookupKey := "d5e9a6499ca74f5b8b958a4b89b7338045b2baa9420e115443a8050e26986564" s := kvsmock.New() - al := setupAccessLogic2() - err := al.AddPublisher(s, &id0.PublicKey) + al := setupAccessLogic() + err := al.AddPublisher(ctx, s, &id0.PublicKey) if err != nil { t.Errorf("AddNewGrantee: expected no error, got %v", err) } - err = al.AddGrantee(s, &id0.PublicKey, &id1.PublicKey, nil) + err = al.AddGrantee(ctx, s, &id0.PublicKey, &id1.PublicKey, nil) if err != nil { t.Errorf("AddNewGrantee: expected no error, got %v", err) } - err = al.AddGrantee(s, &id0.PublicKey, &id2.PublicKey, nil) + err = al.AddGrantee(ctx, s, &id0.PublicKey, &id2.PublicKey, nil) if err != nil { t.Errorf("AddNewGrantee: expected no error, got %v", err) } @@ -209,7 +215,7 @@ func TestAddNewGranteeToContent(t *testing.T) { if err != nil { t.Errorf("AddNewGrantee: expected no error, got %v", err) } - result, _ := s.Get(lookupKeyAsByte) + result, _ := s.Get(ctx, lookupKeyAsByte) hexEncodedEncryptedAK := hex.EncodeToString(result) if len(hexEncodedEncryptedAK) != 64 { t.Errorf("AddNewGrantee: expected encrypted access key length 64, got %d", len(hexEncodedEncryptedAK)) @@ -219,7 +225,7 @@ func TestAddNewGranteeToContent(t *testing.T) { if err != nil { t.Errorf("AddNewGrantee: expected no error, got %v", err) } - result, _ = s.Get(lookupKeyAsByte) + result, _ = s.Get(ctx, lookupKeyAsByte) hexEncodedEncryptedAK = hex.EncodeToString(result) if len(hexEncodedEncryptedAK) != 64 { t.Errorf("AddNewGrantee: expected encrypted access key length 64, got %d", len(hexEncodedEncryptedAK)) @@ -229,7 +235,7 @@ func TestAddNewGranteeToContent(t *testing.T) { if err != nil { t.Errorf("AddNewGrantee: expected no error, got %v", err) } - result, _ = s.Get(lookupKeyAsByte) + result, _ = s.Get(ctx, lookupKeyAsByte) hexEncodedEncryptedAK = hex.EncodeToString(result) if len(hexEncodedEncryptedAK) != 64 { t.Errorf("AddNewGrantee: expected encrypted access key length 64, got %d", len(hexEncodedEncryptedAK)) diff --git a/pkg/dynamicaccess/controller.go b/pkg/dynamicaccess/controller.go index ccdc8c2d7f4..87a24e707b2 100644 --- a/pkg/dynamicaccess/controller.go +++ b/pkg/dynamicaccess/controller.go @@ -1,54 +1,208 @@ package dynamicaccess import ( + "context" "crypto/ecdsa" + "time" + "github.com/ethersphere/bee/v2/pkg/file/loadsave" + "github.com/ethersphere/bee/v2/pkg/file/pipeline" + "github.com/ethersphere/bee/v2/pkg/file/pipeline/builder" + "github.com/ethersphere/bee/v2/pkg/file/redundancy" + "github.com/ethersphere/bee/v2/pkg/kvs" kvsmock "github.com/ethersphere/bee/v2/pkg/kvs/mock" + "github.com/ethersphere/bee/v2/pkg/storage" "github.com/ethersphere/bee/v2/pkg/swarm" ) +type GranteeManager interface { + //PUT /grantees/{grantee} + //body: {publisher?, grantee root hash ,grantee} + Grant(ctx context.Context, granteesAddress swarm.Address, grantee *ecdsa.PublicKey) error + //DELETE /grantees/{grantee} + //body: {publisher?, grantee root hash , grantee} + Revoke(ctx context.Context, granteesAddress swarm.Address, grantee *ecdsa.PublicKey) error + //[ ] + //POST /grantees + //body: {publisher, historyRootHash} + Commit(ctx context.Context, granteesAddress swarm.Address, actRootHash swarm.Address, publisher *ecdsa.PublicKey) (swarm.Address, swarm.Address, error) + + //Post /grantees + //{publisher, addList, removeList} + HandleGrantees(ctx context.Context, rootHash swarm.Address, publisher *ecdsa.PublicKey, addList, removeList []*ecdsa.PublicKey) error + + //GET /grantees/{history root hash} + GetGrantees(ctx context.Context, rootHash swarm.Address) ([]*ecdsa.PublicKey, error) +} + +// TODO: ądd granteeList ref to history metadata to solve inconsistency type Controller interface { - DownloadHandler(timestamp int64, enryptedRef swarm.Address, publisher *ecdsa.PublicKey, tag string) (swarm.Address, error) - UploadHandler(ref swarm.Address, publisher *ecdsa.PublicKey) (swarm.Address, error) + GranteeManager + DownloadHandler(ctx context.Context, timestamp int64, encryptedRef swarm.Address, publisher *ecdsa.PublicKey, historyRootHash swarm.Address) (swarm.Address, error) + UploadHandler(ctx context.Context, reference swarm.Address, publisher *ecdsa.PublicKey, historyRootHash *swarm.Address) (swarm.Address, swarm.Address, swarm.Address, error) } -type defaultController struct { - history History - granteeManager GranteeManager - accessLogic ActLogic +type controller struct { + accessLogic ActLogic + granteeList GranteeList + //[ ]: do we need to protect this with a mutex? + revokeFlag []swarm.Address + getter storage.Getter + putter storage.Putter } -func (c *defaultController) DownloadHandler(timestamp int64, enryptedRef swarm.Address, publisher *ecdsa.PublicKey, tag string) (swarm.Address, error) { - kvs, err := c.history.Lookup(timestamp) +var _ Controller = (*controller)(nil) + +func (c *controller) DownloadHandler( + ctx context.Context, + timestamp int64, + encryptedRef swarm.Address, + publisher *ecdsa.PublicKey, + historyRootHash swarm.Address, +) (swarm.Address, error) { + ls := loadsave.New(c.getter, c.putter, requestPipelineFactory(ctx, c.putter, false, redundancy.NONE)) + history, err := NewHistory(ls, &historyRootHash) if err != nil { - return swarm.EmptyAddress, err + return swarm.ZeroAddress, err } - addr, err := c.accessLogic.DecryptRef(kvs, enryptedRef, publisher) - return addr, err + + kvsRef, err := history.Lookup(ctx, timestamp) + if err != nil { + return swarm.ZeroAddress, err + } + kvs := kvs.New(ls, kvsRef) + return c.accessLogic.DecryptRef(ctx, kvs, encryptedRef, publisher) } -func (c *defaultController) UploadHandler(ref swarm.Address, publisher *ecdsa.PublicKey) (swarm.Address, error) { - kvs, err := c.history.Lookup(0) +// TODO: review return params: how to get back history ref ? +func (c *controller) UploadHandler( + ctx context.Context, + refrefence swarm.Address, + publisher *ecdsa.PublicKey, + historyRootHash *swarm.Address, +) (swarm.Address, swarm.Address, swarm.Address, error) { + ls := loadsave.New(c.getter, c.putter, requestPipelineFactory(ctx, c.putter, false, redundancy.NONE)) + history, err := NewHistory(ls, historyRootHash) + if err != nil { + return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err + } + now := time.Now().Unix() + kvsRef, err := history.Lookup(ctx, now) if err != nil { - return swarm.EmptyAddress, err + return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err } - if kvs == nil { - // new feed - // TODO: putter session to create kvs - kvs = kvsmock.New() - _, err = c.granteeManager.Publish(kvs, publisher) + kvs := kvs.New(ls, kvsRef) + historyRef := swarm.ZeroAddress + if historyRootHash != nil { + historyRef = *historyRootHash + } + if kvsRef.Equal(swarm.ZeroAddress) { + err = c.accessLogic.AddPublisher(ctx, kvs, publisher) + if err != nil { + return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err + } + kvsRef, err = kvs.Save(ctx) if err != nil { - return swarm.EmptyAddress, err + return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err + } + err = history.Add(ctx, kvsRef, &now) + if err != nil { + return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err + } + historyRef, err = history.Store(ctx) + if err != nil { + return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err + } + } + encryptedRef, err := c.accessLogic.EncryptRef(ctx, kvs, publisher, refrefence) + return kvsRef, historyRef, encryptedRef, err +} + +func NewController(ctx context.Context, accessLogic ActLogic, getter storage.Getter, putter storage.Putter) Controller { + return &controller{ + granteeList: nil, + accessLogic: accessLogic, + getter: getter, + putter: putter, + } +} + +func (c *controller) Grant(ctx context.Context, granteesAddress swarm.Address, grantee *ecdsa.PublicKey) error { + return c.granteeList.Add([]*ecdsa.PublicKey{grantee}) +} + +func (c *controller) Revoke(ctx context.Context, granteesAddress swarm.Address, grantee *ecdsa.PublicKey) error { + if !c.isRevokeFlagged(granteesAddress) { + c.setRevokeFlag(granteesAddress, true) + } + return c.granteeList.Remove([]*ecdsa.PublicKey{grantee}) +} + +func (c *controller) Commit(ctx context.Context, granteesAddress swarm.Address, actRootHash swarm.Address, publisher *ecdsa.PublicKey) (swarm.Address, swarm.Address, error) { + var act kvs.KeyValueStore + if c.isRevokeFlagged(granteesAddress) { + act = kvsmock.New() + c.accessLogic.AddPublisher(ctx, act, publisher) + } else { + act = kvsmock.NewReference(actRootHash) + } + + grantees := c.granteeList.Get() + for _, grantee := range grantees { + c.accessLogic.AddGrantee(ctx, act, publisher, grantee, nil) + } + + granteeref, err := c.granteeList.Save(ctx) + if err != nil { + return swarm.EmptyAddress, swarm.EmptyAddress, err + } + + actref, err := act.Save(ctx) + if err != nil { + return swarm.EmptyAddress, swarm.EmptyAddress, err + } + + c.setRevokeFlag(granteesAddress, false) + return granteeref, actref, err +} + +func (c *controller) HandleGrantees(ctx context.Context, granteesAddress swarm.Address, publisher *ecdsa.PublicKey, addList, removeList []*ecdsa.PublicKey) error { + act := kvsmock.New() + + c.accessLogic.AddPublisher(ctx, act, publisher) + for _, grantee := range addList { + c.accessLogic.AddGrantee(ctx, act, publisher, grantee, nil) + } + return nil +} + +func (c *controller) GetGrantees(ctx context.Context, granteeRootHash swarm.Address) ([]*ecdsa.PublicKey, error) { + return c.granteeList.Get(), nil +} + +func (c *controller) isRevokeFlagged(granteeRootHash swarm.Address) bool { + for _, revoke := range c.revokeFlag { + if revoke.Equal(granteeRootHash) { + return true + } + } + return false +} + +func (c *controller) setRevokeFlag(granteeRootHash swarm.Address, set bool) { + if set { + c.revokeFlag = append(c.revokeFlag, granteeRootHash) + } else { + for i, revoke := range c.revokeFlag { + if revoke.Equal(granteeRootHash) { + c.revokeFlag = append(c.revokeFlag[:i], c.revokeFlag[i+1:]...) + } } } - //FIXME: check if kvs is consistent with the grantee list - return c.accessLogic.EncryptRef(kvs, publisher, ref) } -func NewController(history History, granteeManager GranteeManager, accessLogic ActLogic) Controller { - return &defaultController{ - history: history, - granteeManager: granteeManager, - accessLogic: accessLogic, +func requestPipelineFactory(ctx context.Context, s storage.Putter, encrypt bool, rLevel redundancy.Level) func() pipeline.Interface { + return func() pipeline.Interface { + return builder.NewPipelineBuilder(ctx, s, encrypt, rLevel) } } diff --git a/pkg/dynamicaccess/controller_test.go b/pkg/dynamicaccess/controller_test.go index efbfc8d4e42..0512e6481f9 100644 --- a/pkg/dynamicaccess/controller_test.go +++ b/pkg/dynamicaccess/controller_test.go @@ -1,83 +1,93 @@ package dynamicaccess_test import ( + "context" "crypto/ecdsa" - "crypto/elliptic" - "crypto/rand" "encoding/hex" "testing" "time" - "github.com/ethersphere/bee/v2/pkg/crypto" "github.com/ethersphere/bee/v2/pkg/dynamicaccess" - "github.com/ethersphere/bee/v2/pkg/dynamicaccess/mock" "github.com/ethersphere/bee/v2/pkg/encryption" - kvsmock "github.com/ethersphere/bee/v2/pkg/kvs/mock" + "github.com/ethersphere/bee/v2/pkg/file" + "github.com/ethersphere/bee/v2/pkg/kvs" "github.com/ethersphere/bee/v2/pkg/swarm" + "github.com/stretchr/testify/assert" "golang.org/x/crypto/sha3" ) var hashFunc = sha3.NewLegacyKeccak256 -func mockTestHistory(key, val []byte) dynamicaccess.History { - var ( - h = mock.NewHistory() - now = time.Now() - s = kvsmock.New() - ) - _ = s.Put(key, val) - h.Insert(now.AddDate(-3, 0, 0).Unix(), s) - return h +func getHistoryFixture(ctx context.Context, ls file.LoadSaver, al dynamicaccess.ActLogic, publisher *ecdsa.PublicKey) (swarm.Address, error) { + h, err := dynamicaccess.NewHistory(ls, nil) + if err != nil { + return swarm.ZeroAddress, nil + } + pk1 := getPrivKey(1) + pk2 := getPrivKey(2) + + kvs0 := kvs.New(ls, swarm.ZeroAddress) + al.AddPublisher(ctx, kvs0, publisher) + kvs0Ref, _ := kvs0.Save(ctx) + kvs1 := kvs.New(ls, swarm.ZeroAddress) + al.AddGrantee(ctx, kvs1, publisher, &pk1.PublicKey, nil) + al.AddPublisher(ctx, kvs1, publisher) + kvs1Ref, _ := kvs1.Save(ctx) + kvs2 := kvs.New(ls, swarm.ZeroAddress) + al.AddGrantee(ctx, kvs2, publisher, &pk2.PublicKey, nil) + al.AddPublisher(ctx, kvs2, publisher) + kvs2Ref, _ := kvs2.Save(ctx) + firstTime := time.Date(1994, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() + secondTime := time.Date(2000, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() + thirdTime := time.Date(2015, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() + + h.Add(ctx, kvs0Ref, &thirdTime) + h.Add(ctx, kvs1Ref, &firstTime) + h.Add(ctx, kvs2Ref, &secondTime) + return h.Store(ctx) } -func TestDecrypt(t *testing.T) { - pk := getPrivateKey() - ak := encryption.Key([]byte("cica")) - - si := dynamicaccess.NewDefaultSession(pk) - aek, _ := si.Key(&pk.PublicKey, [][]byte{{0}, {1}}) - e2 := encryption.New(aek[1], 0, uint32(0), hashFunc) - peak, _ := e2.Encrypt(ak) - - h := mockTestHistory(aek[0], peak) - al := setupAccessLogic(pk) - gm := dynamicaccess.NewGranteeManager(al) - c := dynamicaccess.NewController(h, gm, al) - eref, ref := prepareEncryptedChunkReference(ak) - // ech := al.EncryptRef(ch, "tag") +// TODO: separate up down test with fixture, now these just check if the flow works at all +func TestController_NewUploadDownload(t *testing.T) { + ctx := context.Background() + publisher := getPrivKey(1) + diffieHellman := dynamicaccess.NewDefaultSession(publisher) + al := dynamicaccess.NewLogic(diffieHellman) + c := dynamicaccess.NewController(ctx, al, mockStorer.ChunkStore(), mockStorer.Cache()) + ref := swarm.RandAddress(t) + _, hRef, encryptedRef, err := c.UploadHandler(ctx, ref, &publisher.PublicKey, nil) + assert.NoError(t, err) + dref, err := c.DownloadHandler(ctx, time.Now().Unix(), encryptedRef, &publisher.PublicKey, hRef) + assert.NoError(t, err) + assert.Equal(t, ref, dref) +} - ts := int64(0) - addr, err := c.DownloadHandler(ts, eref, &pk.PublicKey, "tag") - if err != nil { - t.Fatalf("DownloadHandler() returned an error: %v", err) - } - if !addr.Equal(ref) { - t.Fatalf("Decrypted chunk address: %s is not the expected: %s", addr, ref) - } +func TestController_ExistingUploadDownload(t *testing.T) { + ls := createLs() + ctx := context.Background() + publisher := getPrivKey(0) + diffieHellman := dynamicaccess.NewDefaultSession(publisher) + al := dynamicaccess.NewLogic(diffieHellman) + c := dynamicaccess.NewController(ctx, al, mockStorer.ChunkStore(), mockStorer.Cache()) + ref := swarm.RandAddress(t) + hRef, err := getHistoryFixture(ctx, ls, al, &publisher.PublicKey) + assert.NoError(t, err) + _, hRef, encryptedRef, err := c.UploadHandler(ctx, ref, &publisher.PublicKey, &hRef) + assert.NoError(t, err) + dref, err := c.DownloadHandler(ctx, time.Now().Unix(), encryptedRef, &publisher.PublicKey, hRef) + assert.NoError(t, err) + assert.Equal(t, ref, dref) } -func TestEncrypt(t *testing.T) { - pk := getPrivateKey() - ak := encryption.Key([]byte("cica")) +func TestControllerGrant(t *testing.T) { +} - si := dynamicaccess.NewDefaultSession(pk) - aek, _ := si.Key(&pk.PublicKey, [][]byte{{0}, {1}}) - e2 := encryption.New(aek[1], 0, uint32(0), hashFunc) - peak, _ := e2.Encrypt(ak) +func TestControllerRevoke(t *testing.T) { - h := mockTestHistory(aek[0], peak) - al := setupAccessLogic(pk) - gm := dynamicaccess.NewGranteeManager(al) - c := dynamicaccess.NewController(h, gm, al) - eref, ref := prepareEncryptedChunkReference(ak) +} - key1, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - gm.Add([]*ecdsa.PublicKey{&key1.PublicKey}) +func TestControllerCommit(t *testing.T) { - addr, _ := c.UploadHandler(ref, &pk.PublicKey) - if !addr.Equal(eref) { - t.Fatalf("Decrypted chunk address: %s is not the expected: %s", addr, eref) - } } func prepareEncryptedChunkReference(ak []byte) (swarm.Address, swarm.Address) { @@ -85,14 +95,7 @@ func prepareEncryptedChunkReference(ak []byte) (swarm.Address, swarm.Address) { e1 := encryption.New(ak, 0, uint32(0), hashFunc) ech, err := e1.Encrypt(addr) if err != nil { - return swarm.EmptyAddress, swarm.NewAddress(addr) + return swarm.EmptyAddress, swarm.EmptyAddress } return swarm.NewAddress(ech), swarm.NewAddress(addr) } - -func getPrivateKey() *ecdsa.PrivateKey { - data, _ := hex.DecodeString("c786dd84b61485de12146fd9c4c02d87e8fd95f0542765cb7fc3d2e428c0bcfa") - - privKey, _ := crypto.DecodeSecp256k1PrivateKey(data) - return privKey -} diff --git a/pkg/dynamicaccess/grantee.go b/pkg/dynamicaccess/grantee.go index d850fc047cc..8499cfc4f04 100644 --- a/pkg/dynamicaccess/grantee.go +++ b/pkg/dynamicaccess/grantee.go @@ -19,7 +19,7 @@ type GranteeList interface { Add(addList []*ecdsa.PublicKey) error Remove(removeList []*ecdsa.PublicKey) error Get() []*ecdsa.PublicKey - Save() (swarm.Address, error) + Save(ctx context.Context) (swarm.Address, error) } type GranteeListStruct struct { @@ -78,8 +78,8 @@ func (g *GranteeListStruct) Add(addList []*ecdsa.PublicKey) error { return nil } -func (g *GranteeListStruct) Save() (swarm.Address, error) { - refBytes, err := g.loadSave.Save(context.Background(), g.grantees) +func (g *GranteeListStruct) Save(ctx context.Context) (swarm.Address, error) { + refBytes, err := g.loadSave.Save(ctx, g.grantees) if err != nil { return swarm.ZeroAddress, fmt.Errorf("grantee save error: %w", err) } @@ -132,3 +132,7 @@ func NewGranteeList(ls file.LoadSaver, putter storer.PutterSession, reference sw putter: putter, } } + +func (g *GranteeListStruct) Store() (swarm.Address, error) { + return swarm.EmptyAddress, nil +} diff --git a/pkg/dynamicaccess/grantee_manager.go b/pkg/dynamicaccess/grantee_manager.go deleted file mode 100644 index 1fac35a38bd..00000000000 --- a/pkg/dynamicaccess/grantee_manager.go +++ /dev/null @@ -1,47 +0,0 @@ -package dynamicaccess - -import ( - "crypto/ecdsa" - - "github.com/ethersphere/bee/v2/pkg/dynamicaccess/mock" - "github.com/ethersphere/bee/v2/pkg/kvs" - "github.com/ethersphere/bee/v2/pkg/swarm" -) - -type GranteeManager interface { - Get() []*ecdsa.PublicKey - Add(addList []*ecdsa.PublicKey) error - Publish(kvs kvs.KeyValueStore, publisher *ecdsa.PublicKey) (swarm.Address, error) - - // HandleGrantees(addList, removeList []*ecdsa.PublicKey) *Act - - // Load(grantee Grantee) - // Save() -} - -var _ GranteeManager = (*granteeManager)(nil) - -type granteeManager struct { - accessLogic ActLogic - granteeList *mock.GranteeListStructMock -} - -func NewGranteeManager(al ActLogic) *granteeManager { - return &granteeManager{accessLogic: al, granteeList: mock.NewGranteeList()} -} - -func (gm *granteeManager) Get() []*ecdsa.PublicKey { - return gm.granteeList.Get() -} - -func (gm *granteeManager) Add(addList []*ecdsa.PublicKey) error { - return gm.granteeList.Add(addList) -} - -func (gm *granteeManager) Publish(kvs kvs.KeyValueStore, publisher *ecdsa.PublicKey) (swarm.Address, error) { - err := gm.accessLogic.AddPublisher(kvs, publisher) - for _, grantee := range gm.granteeList.Get() { - err = gm.accessLogic.AddGrantee(kvs, publisher, grantee, nil) - } - return swarm.EmptyAddress, err -} diff --git a/pkg/dynamicaccess/grantee_manager_test.go b/pkg/dynamicaccess/grantee_manager_test.go deleted file mode 100644 index bb01c13cd85..00000000000 --- a/pkg/dynamicaccess/grantee_manager_test.go +++ /dev/null @@ -1,38 +0,0 @@ -package dynamicaccess_test - -import ( - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rand" - "fmt" - "testing" - - "github.com/ethersphere/bee/v2/pkg/dynamicaccess" - kvsmock "github.com/ethersphere/bee/v2/pkg/kvs/mock" -) - -func setupAccessLogic(privateKey *ecdsa.PrivateKey) dynamicaccess.ActLogic { - si := dynamicaccess.NewDefaultSession(privateKey) - al := dynamicaccess.NewLogic(si) - - return al -} - -func TestAdd(t *testing.T) { - m := dynamicaccess.NewGranteeManager(setupAccessLogic(getPrivateKey())) - pub, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - - id1, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - id2, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - err := m.Add([]*ecdsa.PublicKey{&id1.PublicKey}) - if err != nil { - t.Errorf("Add() returned an error") - } - err = m.Add([]*ecdsa.PublicKey{&id2.PublicKey}) - if err != nil { - t.Errorf("Add() returned an error") - } - s := kvsmock.New() - m.Publish(s, &pub.PublicKey) - fmt.Println("") -} diff --git a/pkg/dynamicaccess/grantee_test.go b/pkg/dynamicaccess/grantee_test.go index b644f5896c5..5730911ad74 100644 --- a/pkg/dynamicaccess/grantee_test.go +++ b/pkg/dynamicaccess/grantee_test.go @@ -148,19 +148,20 @@ func TestGranteeRemove(t *testing.T) { } func TestGranteeSave(t *testing.T) { + ctx := context.Background() keys, err := generateKeyListFixture() if err != nil { t.Errorf("key generation error: %v", err) } t.Run("Save empty grantee list return NO error", func(t *testing.T) { gl := dynamicaccess.NewGranteeList(createLs(), mockStorer.DirectUpload(), swarm.ZeroAddress) - _, err := gl.Save() + _, err := gl.Save(ctx) assert.NoError(t, err) }) t.Run("Save not empty grantee list return valid swarm address", func(t *testing.T) { gl := dynamicaccess.NewGranteeList(createLs(), mockStorer.DirectUpload(), swarm.ZeroAddress) err = gl.Add(keys) - ref, err := gl.Save() + ref, err := gl.Save(ctx) assert.NoError(t, err) assert.True(t, ref.IsValidNonEmpty()) }) @@ -172,7 +173,7 @@ func TestGranteeSave(t *testing.T) { err := gl1.Add(keys) assert.NoError(t, err) - ref, err := gl1.Save() + ref, err := gl1.Save(ctx) assert.NoError(t, err) gl2 := dynamicaccess.NewGranteeList(ls, putter, ref) @@ -188,7 +189,7 @@ func TestGranteeSave(t *testing.T) { err := gl1.Add(keys) assert.NoError(t, err) - ref, err := gl1.Save() + ref, err := gl1.Save(ctx) assert.NoError(t, err) // New KVS diff --git a/pkg/dynamicaccess/mock/service.go b/pkg/dynamicaccess/mock/service.go new file mode 100644 index 00000000000..2d19c3ed4e3 --- /dev/null +++ b/pkg/dynamicaccess/mock/service.go @@ -0,0 +1,162 @@ +// Copyright 2020 The Swarm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package mock + +import ( + "context" + "crypto/ecdsa" + "encoding/hex" + "fmt" + "time" + + "github.com/ethersphere/bee/v2/pkg/crypto" + "github.com/ethersphere/bee/v2/pkg/dynamicaccess" + "github.com/ethersphere/bee/v2/pkg/encryption" + "github.com/ethersphere/bee/v2/pkg/file" + "github.com/ethersphere/bee/v2/pkg/file/loadsave" + "github.com/ethersphere/bee/v2/pkg/file/pipeline" + "github.com/ethersphere/bee/v2/pkg/file/pipeline/builder" + "github.com/ethersphere/bee/v2/pkg/file/redundancy" + "github.com/ethersphere/bee/v2/pkg/storage" + mockstorer "github.com/ethersphere/bee/v2/pkg/storer/mock" + "github.com/ethersphere/bee/v2/pkg/swarm" + "golang.org/x/crypto/sha3" +) + +type mockDacService struct { + historyMap map[string]dynamicaccess.History + refMap map[string]swarm.Address + acceptAll bool + publisher string + encrypter encryption.Interface + ls file.LoadSaver +} + +type optionFunc func(*mockDacService) + +// Option is an option passed to a mock dynamicaccess Service. +type Option interface { + apply(*mockDacService) +} + +func (f optionFunc) apply(r *mockDacService) { f(r) } + +// New creates a new mock dynamicaccess service. +func New(o ...Option) dynamicaccess.Service { + storer := mockstorer.New() + m := &mockDacService{ + historyMap: make(map[string]dynamicaccess.History), + refMap: make(map[string]swarm.Address), + publisher: "", + encrypter: encryption.New(encryption.Key("b6ee086390c280eeb9824c331a4427596f0c8510d5564bc1b6168d0059a46e2b"), 0, uint32(0), sha3.NewLegacyKeccak256), + ls: loadsave.New(storer.ChunkStore(), storer.Cache(), requestPipelineFactory(context.Background(), storer.Cache(), false, redundancy.NONE)), + } + for _, v := range o { + v.apply(m) + } + + return m +} + +// WithAcceptAll sets the mock to return fixed references on every call to DownloadHandler. +func WithAcceptAll() Option { + return optionFunc(func(m *mockDacService) { m.acceptAll = true }) +} + +func WithHistory(h dynamicaccess.History, ref string) Option { + return optionFunc(func(m *mockDacService) { + m.historyMap = map[string]dynamicaccess.History{ref: h} + }) +} + +func WithPublisher(ref string) Option { + return optionFunc(func(m *mockDacService) { + m.publisher = ref + m.encrypter = encryption.New(encryption.Key(ref), 0, uint32(0), sha3.NewLegacyKeccak256) + }) +} + +func (m *mockDacService) DownloadHandler(ctx context.Context, timestamp int64, encryptedRef swarm.Address, publisher *ecdsa.PublicKey, historyRootHash swarm.Address) (swarm.Address, error) { + if m.acceptAll { + return swarm.ParseHexAddress("36e6c1bbdfee6ac21485d5f970479fd1df458d36df9ef4e8179708ed46da557f") + } + + publicKeyBytes := crypto.EncodeSecp256k1PublicKey(publisher) + p := hex.EncodeToString(publicKeyBytes) + if m.publisher != "" && m.publisher != p { + return swarm.ZeroAddress, fmt.Errorf("incorrect publisher") + } + + h, exists := m.historyMap[historyRootHash.String()] + if !exists { + return swarm.ZeroAddress, fmt.Errorf("history not found") + } + kvsRef, err := h.Lookup(ctx, timestamp) + if kvsRef.Equal(swarm.ZeroAddress) || err != nil { + return swarm.ZeroAddress, fmt.Errorf("kvs not found") + } + return m.refMap[encryptedRef.String()], nil +} + +func (m *mockDacService) UploadHandler(ctx context.Context, reference swarm.Address, publisher *ecdsa.PublicKey, historyRootHash *swarm.Address) (swarm.Address, swarm.Address, swarm.Address, error) { + historyRef, _ := swarm.ParseHexAddress("67bdf80a9bbea8eca9c8480e43fdceb485d2d74d5708e45144b8c4adacd13d9c") + kvsRef, _ := swarm.ParseHexAddress("3339613565613837623134316665343461613630396333333237656364383934") + if m.acceptAll { + encryptedRef, _ := swarm.ParseHexAddress("fc4e9fe978991257b897d987bc4ff13058b66ef45a53189a0b4fe84bb3346396") + return kvsRef, historyRef, encryptedRef, nil + } + var ( + h dynamicaccess.History + exists bool + ) + now := time.Now().Unix() + if historyRootHash != nil { + historyRef = *historyRootHash + h, exists = m.historyMap[historyRef.String()] + if !exists { + return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, fmt.Errorf("history not found") + } + kvsRef, _ = h.Lookup(ctx, now) + } else { + h, _ = dynamicaccess.NewHistory(m.ls, nil) + h.Add(ctx, kvsRef, &now) + historyRef, _ = h.Store(ctx) + m.historyMap[historyRef.String()] = h + } + if kvsRef.Equal(swarm.ZeroAddress) { + return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, fmt.Errorf("kvs not found") + } + encryptedRef, _ := m.encrypter.Encrypt(reference.Bytes()) + m.refMap[(hex.EncodeToString(encryptedRef))] = reference + return kvsRef, historyRef, swarm.NewAddress(encryptedRef), nil +} + +func (m *mockDacService) Close() error { + return nil +} + +func (m *mockDacService) Grant(ctx context.Context, granteesAddress swarm.Address, grantee *ecdsa.PublicKey) error { + return nil +} +func (m *mockDacService) Revoke(ctx context.Context, granteesAddress swarm.Address, grantee *ecdsa.PublicKey) error { + return nil +} +func (m *mockDacService) Commit(ctx context.Context, granteesAddress swarm.Address, actRootHash swarm.Address, publisher *ecdsa.PublicKey) (swarm.Address, swarm.Address, error) { + return swarm.ZeroAddress, swarm.ZeroAddress, nil +} +func (m *mockDacService) HandleGrantees(ctx context.Context, rootHash swarm.Address, publisher *ecdsa.PublicKey, addList, removeList []*ecdsa.PublicKey) error { + return nil +} +func (m *mockDacService) GetGrantees(ctx context.Context, rootHash swarm.Address) ([]*ecdsa.PublicKey, error) { + return nil, nil +} + +func requestPipelineFactory(ctx context.Context, s storage.Putter, encrypt bool, rLevel redundancy.Level) func() pipeline.Interface { + return func() pipeline.Interface { + return builder.NewPipelineBuilder(ctx, s, encrypt, rLevel) + } +} + +var _ dynamicaccess.Controller = (*mockDacService)(nil) diff --git a/pkg/dynamicaccess/service.go b/pkg/dynamicaccess/service.go new file mode 100644 index 00000000000..c87b74ee60c --- /dev/null +++ b/pkg/dynamicaccess/service.go @@ -0,0 +1,39 @@ +package dynamicaccess + +import ( + "context" + "crypto/ecdsa" + "io" + + "github.com/ethersphere/bee/v2/pkg/swarm" +) + +type Service interface { + DownloadHandler(ctx context.Context, timestamp int64, encryptedRef swarm.Address, publisher *ecdsa.PublicKey, historyRootHash swarm.Address) (swarm.Address, error) + UploadHandler(ctx context.Context, reference swarm.Address, publisher *ecdsa.PublicKey, historyRootHash *swarm.Address) (swarm.Address, swarm.Address, swarm.Address, error) + io.Closer +} + +// TODO: is service needed at all? -> it is just a wrapper around controller +type service struct { + controller Controller +} + +func (s *service) DownloadHandler(ctx context.Context, timestamp int64, encryptedRef swarm.Address, publisher *ecdsa.PublicKey, historyRootHash swarm.Address) (swarm.Address, error) { + return s.controller.DownloadHandler(ctx, timestamp, encryptedRef, publisher, historyRootHash) +} + +func (s *service) UploadHandler(ctx context.Context, reference swarm.Address, publisher *ecdsa.PublicKey, historyRootHash *swarm.Address) (swarm.Address, swarm.Address, swarm.Address, error) { + return s.controller.UploadHandler(ctx, reference, publisher, historyRootHash) +} + +// TODO: what to do in close ? +func (s *service) Close() error { + return nil +} + +func NewService(controller Controller) (Service, error) { + return &service{ + controller: controller, + }, nil +} diff --git a/pkg/kvs/kvs.go b/pkg/kvs/kvs.go index fcb5fc668bb..42a07da6738 100644 --- a/pkg/kvs/kvs.go +++ b/pkg/kvs/kvs.go @@ -11,27 +11,24 @@ import ( "github.com/ethersphere/bee/v2/pkg/file" "github.com/ethersphere/bee/v2/pkg/manifest" - "github.com/ethersphere/bee/v2/pkg/storer" "github.com/ethersphere/bee/v2/pkg/swarm" ) type KeyValueStore interface { - Get(key []byte) ([]byte, error) - Put(key, value []byte) error - Save() (swarm.Address, error) + Get(ctx context.Context, key []byte) ([]byte, error) + Put(ctx context.Context, key, value []byte) error + Save(ctx context.Context) (swarm.Address, error) } type keyValueStore struct { manifest manifest.Interface - putter storer.PutterSession putCnt int } var _ KeyValueStore = (*keyValueStore)(nil) -// TODO: pass context as dep. -func (s *keyValueStore) Get(key []byte) ([]byte, error) { - entry, err := s.manifest.Lookup(context.Background(), hex.EncodeToString(key)) +func (s *keyValueStore) Get(ctx context.Context, key []byte) ([]byte, error) { + entry, err := s.manifest.Lookup(ctx, hex.EncodeToString(key)) if err != nil { return nil, err } @@ -39,8 +36,8 @@ func (s *keyValueStore) Get(key []byte) ([]byte, error) { return ref.Bytes(), nil } -func (s *keyValueStore) Put(key []byte, value []byte) error { - err := s.manifest.Add(context.Background(), hex.EncodeToString(key), manifest.NewEntry(swarm.NewAddress(value), map[string]string{})) +func (s *keyValueStore) Put(ctx context.Context, key []byte, value []byte) error { + err := s.manifest.Add(ctx, hex.EncodeToString(key), manifest.NewEntry(swarm.NewAddress(value), map[string]string{})) if err != nil { return err } @@ -48,15 +45,11 @@ func (s *keyValueStore) Put(key []byte, value []byte) error { return nil } -func (s *keyValueStore) Save() (swarm.Address, error) { +func (s *keyValueStore) Save(ctx context.Context) (swarm.Address, error) { if s.putCnt == 0 { return swarm.ZeroAddress, errors.New("nothing to save") } - ref, err := s.manifest.Store(context.Background()) - if err != nil { - return swarm.ZeroAddress, err - } - err = s.putter.Done(ref) + ref, err := s.manifest.Store(ctx) if err != nil { return swarm.ZeroAddress, err } @@ -64,7 +57,7 @@ func (s *keyValueStore) Save() (swarm.Address, error) { return ref, nil } -func New(ls file.LoadSaver, putter storer.PutterSession, rootHash swarm.Address) KeyValueStore { +func New(ls file.LoadSaver, rootHash swarm.Address) KeyValueStore { var ( manif manifest.Interface err error @@ -80,6 +73,5 @@ func New(ls file.LoadSaver, putter storer.PutterSession, rootHash swarm.Address) return &keyValueStore{ manifest: manif, - putter: putter, } } diff --git a/pkg/kvs/kvs_test.go b/pkg/kvs/kvs_test.go index 9edfe48062c..5c6f75e2379 100644 --- a/pkg/kvs/kvs_test.go +++ b/pkg/kvs/kvs_test.go @@ -38,24 +38,25 @@ func keyValuePair(t *testing.T) ([]byte, []byte) { func TestKvs(t *testing.T) { - s := kvs.New(createLs(), mockStorer.DirectUpload(), swarm.ZeroAddress) + s := kvs.New(createLs(), swarm.ZeroAddress) key, val := keyValuePair(t) + ctx := context.Background() t.Run("Get non-existent key should return error", func(t *testing.T) { - _, err := s.Get([]byte{1}) + _, err := s.Get(ctx, []byte{1}) assert.Error(t, err) }) t.Run("Multiple Get with same key, no error", func(t *testing.T) { - err := s.Put(key, val) + err := s.Put(ctx, key, val) assert.NoError(t, err) // get #1 - v, err := s.Get(key) + v, err := s.Get(ctx, key) assert.NoError(t, err) assert.Equal(t, val, v) // get #2 - v, err = s.Get(key) + v, err = s.Get(ctx, key) assert.NoError(t, err) assert.Equal(t, val, v) }) @@ -105,9 +106,9 @@ func TestKvs(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - err := s.Put(tc.key, tc.val) + err := s.Put(ctx, tc.key, tc.val) assert.NoError(t, err) - retVal, err := s.Get(tc.key) + retVal, err := s.Get(ctx, tc.key) assert.NoError(t, err) assert.Equal(t, tc.val, retVal) }) @@ -116,53 +117,53 @@ func TestKvs(t *testing.T) { } func TestKvs_Save(t *testing.T) { + ctx := context.Background() + key1, val1 := keyValuePair(t) key2, val2 := keyValuePair(t) t.Run("Save empty KVS return error", func(t *testing.T) { - s := kvs.New(createLs(), mockStorer.DirectUpload(), swarm.ZeroAddress) - _, err := s.Save() + s := kvs.New(createLs(), swarm.ZeroAddress) + _, err := s.Save(ctx) assert.Error(t, err) }) t.Run("Save not empty KVS return valid swarm address", func(t *testing.T) { - s := kvs.New(createLs(), mockStorer.DirectUpload(), swarm.ZeroAddress) - s.Put(key1, val1) - ref, err := s.Save() + s := kvs.New(createLs(), swarm.ZeroAddress) + s.Put(ctx, key1, val1) + ref, err := s.Save(ctx) assert.NoError(t, err) assert.True(t, ref.IsValidNonEmpty()) }) t.Run("Save KVS with one item, no error, pre-save value exist", func(t *testing.T) { ls := createLs() - putter := mockStorer.DirectUpload() - s1 := kvs.New(ls, putter, swarm.ZeroAddress) + s1 := kvs.New(ls, swarm.ZeroAddress) - err := s1.Put(key1, val1) + err := s1.Put(ctx, key1, val1) assert.NoError(t, err) - ref, err := s1.Save() + ref, err := s1.Save(ctx) assert.NoError(t, err) - s2 := kvs.New(ls, putter, ref) - val, err := s2.Get(key1) + s2 := kvs.New(ls, ref) + val, err := s2.Get(ctx, key1) assert.NoError(t, err) assert.Equal(t, val1, val) }) t.Run("Save KVS and add one item, no error, after-save value exist", func(t *testing.T) { ls := createLs() - putter := mockStorer.DirectUpload() - kvs1 := kvs.New(ls, putter, swarm.ZeroAddress) + kvs1 := kvs.New(ls, swarm.ZeroAddress) - err := kvs1.Put(key1, val1) + err := kvs1.Put(ctx, key1, val1) assert.NoError(t, err) - ref, err := kvs1.Save() + ref, err := kvs1.Save(ctx) assert.NoError(t, err) // New KVS - kvs2 := kvs.New(ls, putter, ref) - err = kvs2.Put(key2, val2) + kvs2 := kvs.New(ls, ref) + err = kvs2.Put(ctx, key2, val2) assert.NoError(t, err) - val, err := kvs2.Get(key2) + val, err := kvs2.Get(ctx, key2) assert.NoError(t, err) assert.Equal(t, val2, val) }) diff --git a/pkg/kvs/mock/kvs.go b/pkg/kvs/mock/kvs.go index 78282934bf2..0203cc52ce6 100644 --- a/pkg/kvs/mock/kvs.go +++ b/pkg/kvs/mock/kvs.go @@ -1,6 +1,7 @@ package mock import ( + "context" "encoding/hex" "sync" @@ -44,13 +45,13 @@ type mockKeyValueStore struct { var _ kvs.KeyValueStore = (*mockKeyValueStore)(nil) -func (m *mockKeyValueStore) Get(key []byte) ([]byte, error) { +func (m *mockKeyValueStore) Get(_ context.Context, key []byte) ([]byte, error) { mem := getMemory() val := mem[m.address.String()][hex.EncodeToString(key)] return val, nil } -func (m *mockKeyValueStore) Put(key []byte, value []byte) error { +func (m *mockKeyValueStore) Put(_ context.Context, key []byte, value []byte) error { mem := getMemory() if _, ok := mem[m.address.String()]; !ok { mem[m.address.String()] = make(map[string][]byte) @@ -59,7 +60,7 @@ func (m *mockKeyValueStore) Put(key []byte, value []byte) error { return nil } -func (m *mockKeyValueStore) Save() (swarm.Address, error) { +func (m *mockKeyValueStore) Save(ctx context.Context) (swarm.Address, error) { return m.address, nil } diff --git a/pkg/node/devnode.go b/pkg/node/devnode.go index 1b090654295..b7b35809f9c 100644 --- a/pkg/node/devnode.go +++ b/pkg/node/devnode.go @@ -22,6 +22,7 @@ import ( "github.com/ethersphere/bee/v2/pkg/auth" "github.com/ethersphere/bee/v2/pkg/bzz" "github.com/ethersphere/bee/v2/pkg/crypto" + "github.com/ethersphere/bee/v2/pkg/dynamicaccess" "github.com/ethersphere/bee/v2/pkg/feeds/factory" "github.com/ethersphere/bee/v2/pkg/log" mockP2P "github.com/ethersphere/bee/v2/pkg/p2p/mock" @@ -66,6 +67,7 @@ type DevBee struct { localstoreCloser io.Closer apiCloser io.Closer pssCloser io.Closer + dacCloser io.Closer errorLogWriter io.Writer apiServer *http.Server debugAPIServer *http.Server @@ -234,6 +236,15 @@ func NewDevBee(logger log.Logger, o *DevOptions) (b *DevBee, err error) { } b.localstoreCloser = localStore + session := dynamicaccess.NewDefaultSession(mockKey) + actLogic := dynamicaccess.NewLogic(session) + ctrl := dynamicaccess.NewController(context.Background(), actLogic, localStore.ChunkStore(), localStore.Cache()) + dac, err := dynamicaccess.NewService(ctrl) + if err != nil { + return nil, fmt.Errorf("dac service: %w", err) + } + b.dacCloser = dac + pssService := pss.New(mockKey, logger) b.pssCloser = pssService @@ -383,6 +394,7 @@ func NewDevBee(logger log.Logger, o *DevOptions) (b *DevBee, err error) { Pss: pssService, FeedFactory: mockFeeds, Post: post, + Dac: dac, PostageContract: postageContract, Staking: mockStaking, Steward: mockSteward, @@ -491,6 +503,7 @@ func (b *DevBee) Shutdown() error { } tryClose(b.pssCloser, "pss") + tryClose(b.dacCloser, "dac") tryClose(b.tracerCloser, "tracer") tryClose(b.stateStoreCloser, "statestore") tryClose(b.localstoreCloser, "localstore") diff --git a/pkg/node/node.go b/pkg/node/node.go index fa3f3f861b5..8d2cdf5cff3 100644 --- a/pkg/node/node.go +++ b/pkg/node/node.go @@ -30,6 +30,7 @@ import ( "github.com/ethersphere/bee/v2/pkg/auth" "github.com/ethersphere/bee/v2/pkg/config" "github.com/ethersphere/bee/v2/pkg/crypto" + "github.com/ethersphere/bee/v2/pkg/dynamicaccess" "github.com/ethersphere/bee/v2/pkg/feeds/factory" "github.com/ethersphere/bee/v2/pkg/hive" "github.com/ethersphere/bee/v2/pkg/log" @@ -117,6 +118,7 @@ type Bee struct { shutdownInProgress bool shutdownMutex sync.Mutex syncingStopped *syncutil.Signaler + dacCloser io.Closer } type Options struct { @@ -202,6 +204,7 @@ func NewBee( logger log.Logger, libp2pPrivateKey, pssPrivateKey *ecdsa.PrivateKey, + session dynamicaccess.Session, o *Options, ) (b *Bee, err error) { tracer, tracerCloser, err := tracing.NewTracer(&tracing.Options{ @@ -778,6 +781,14 @@ func NewBee( b.localstoreCloser = localStore evictFn = func(id []byte) error { return localStore.EvictBatch(context.Background(), id) } + actLogic := dynamicaccess.NewLogic(session) + ctrl := dynamicaccess.NewController(ctx, actLogic, localStore.ChunkStore(), localStore.Cache()) + dac, err := dynamicaccess.NewService(ctrl) + if err != nil { + return nil, fmt.Errorf("dac service: %w", err) + } + b.dacCloser = dac + var ( syncErr atomic.Value syncStatus atomic.Value @@ -1093,6 +1104,7 @@ func NewBee( Pss: pssService, FeedFactory: feedFactory, Post: post, + Dac: dac, PostageContract: postageStampContractService, Staking: stakingContract, Steward: steward, @@ -1355,6 +1367,7 @@ func (b *Bee) Shutdown() error { c() } + tryClose(b.dacCloser, "dac") tryClose(b.tracerCloser, "tracer") tryClose(b.topologyCloser, "topology driver") tryClose(b.storageIncetivesCloser, "storage incentives agent") diff --git a/pkg/soc/testing/soc.go b/pkg/soc/testing/soc.go index e5325f464b1..9516a721d83 100644 --- a/pkg/soc/testing/soc.go +++ b/pkg/soc/testing/soc.go @@ -5,6 +5,7 @@ package testing import ( + "crypto/ecdsa" "testing" "github.com/ethersphere/bee/v2/pkg/cac" @@ -70,3 +71,38 @@ func GenerateMockSOC(t *testing.T, data []byte) *MockSOC { WrappedChunk: ch, } } + +// GenerateMockSOC generates a valid mocked SOC from given data and key. +func GenerateMockSOCWithKey(t *testing.T, data []byte, privKey *ecdsa.PrivateKey) *MockSOC { + t.Helper() + + signer := crypto.NewDefaultSigner(privKey) + owner, err := signer.EthereumAddress() + if err != nil { + t.Fatal(err) + } + + ch, err := cac.New(data) + if err != nil { + t.Fatal(err) + } + + id := make([]byte, swarm.HashSize) + hasher := swarm.NewHasher() + _, err = hasher.Write(append(id, ch.Address().Bytes()...)) + if err != nil { + t.Fatal(err) + } + + signature, err := signer.Sign(hasher.Sum(nil)) + if err != nil { + t.Fatal(err) + } + + return &MockSOC{ + ID: id, + Owner: owner.Bytes(), + Signature: signature, + WrappedChunk: ch, + } +} From 37fbb683e6347f67e6b1c1f56964445cb5e7157e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A1lint=20Ujv=C3=A1ri?= <58116288+bosi95@users.noreply.github.com> Date: Fri, 19 Apr 2024 16:10:24 +0200 Subject: [PATCH 24/34] Act refactor api and ctrl (#36) * Refactor: newreference pattern for history, kvs, grantee * CHG: history metadata to store granteelist reference * History test with metadata * Remove putter from grantee --- pkg/api/bytes.go | 6 +-- pkg/api/bzz.go | 8 +-- pkg/api/chunk.go | 10 ++-- pkg/api/chunk_address.go | 2 +- pkg/api/dirs.go | 2 +- pkg/api/dynamicaccess.go | 37 +++++++------ pkg/api/dynamicaccess_test.go | 18 +++---- pkg/api/feed.go | 10 ++-- pkg/api/soc.go | 8 +-- pkg/dynamicaccess/accesslogic.go | 4 +- pkg/dynamicaccess/controller.go | 81 ++++++++++++++++++---------- pkg/dynamicaccess/controller_test.go | 22 ++++---- pkg/dynamicaccess/grantee.go | 32 ++++------- pkg/dynamicaccess/grantee_test.go | 22 +++----- pkg/dynamicaccess/history.go | 49 ++++++++++------- pkg/dynamicaccess/history_test.go | 61 ++++++++++++++------- pkg/dynamicaccess/mock/service.go | 28 +++++----- pkg/dynamicaccess/service.go | 10 ++-- pkg/kvs/kvs.go | 25 +++++---- pkg/kvs/kvs_test.go | 19 ++++--- 20 files changed, 256 insertions(+), 198 deletions(-) diff --git a/pkg/api/bytes.go b/pkg/api/bytes.go index dc3735a497b..a84ec2936de 100644 --- a/pkg/api/bytes.go +++ b/pkg/api/bytes.go @@ -40,7 +40,7 @@ func (s *Service) bytesUploadHandler(w http.ResponseWriter, r *http.Request) { Encrypt bool `map:"Swarm-Encrypt"` RLevel redundancy.Level `map:"Swarm-Redundancy-Level"` Act bool `map:"Swarm-Act"` - HistoryAddress *swarm.Address `map:"Swarm-Act-History-Address"` + HistoryAddress swarm.Address `map:"Swarm-Act-History-Address"` }{} if response := s.mapStructure(r.Header, &headers); response != nil { response("invalid header params", logger, w) @@ -161,7 +161,7 @@ func (s *Service) bytesGetHandler(w http.ResponseWriter, r *http.Request) { } address := paths.Address - if v := getAddressFromContext(r.Context()); !v.Equal(swarm.ZeroAddress) { + if v := getAddressFromContext(r.Context()); !v.IsZero() { address = v } @@ -184,7 +184,7 @@ func (s *Service) bytesHeadHandler(w http.ResponseWriter, r *http.Request) { } address := paths.Address - if v := getAddressFromContext(r.Context()); !v.Equal(swarm.ZeroAddress) { + if v := getAddressFromContext(r.Context()); !v.IsZero() { address = v } diff --git a/pkg/api/bzz.go b/pkg/api/bzz.go index 241e30cf165..6319624a17d 100644 --- a/pkg/api/bzz.go +++ b/pkg/api/bzz.go @@ -72,7 +72,7 @@ func (s *Service) bzzUploadHandler(w http.ResponseWriter, r *http.Request) { IsDir bool `map:"Swarm-Collection"` RLevel redundancy.Level `map:"Swarm-Redundancy-Level"` Act bool `map:"Swarm-Act"` - HistoryAddress *swarm.Address `map:"Swarm-Act-History-Address"` + HistoryAddress swarm.Address `map:"Swarm-Act-History-Address"` }{} if response := s.mapStructure(r.Header, &headers); response != nil { response("invalid header params", logger, w) @@ -158,7 +158,7 @@ func (s *Service) fileUploadHandler( tagID uint64, rLevel redundancy.Level, act bool, - historyAddress *swarm.Address, + historyAddress swarm.Address, ) { queries := struct { FileName string `map:"name" validate:"startsnotwith=/"` @@ -310,7 +310,7 @@ func (s *Service) bzzDownloadHandler(w http.ResponseWriter, r *http.Request) { } address := paths.Address - if v := getAddressFromContext(r.Context()); !v.Equal(swarm.ZeroAddress) { + if v := getAddressFromContext(r.Context()); !v.IsZero() { address = v } @@ -334,7 +334,7 @@ func (s *Service) bzzHeadHandler(w http.ResponseWriter, r *http.Request) { } address := paths.Address - if v := getAddressFromContext(r.Context()); !v.Equal(swarm.ZeroAddress) { + if v := getAddressFromContext(r.Context()); !v.IsZero() { address = v } diff --git a/pkg/api/chunk.go b/pkg/api/chunk.go index 13738f70d39..496f7f8d306 100644 --- a/pkg/api/chunk.go +++ b/pkg/api/chunk.go @@ -30,10 +30,10 @@ func (s *Service) chunkUploadHandler(w http.ResponseWriter, r *http.Request) { logger := s.logger.WithName("post_chunk").Build() headers := struct { - BatchID []byte `map:"Swarm-Postage-Batch-Id" validate:"required"` - SwarmTag uint64 `map:"Swarm-Tag"` - Act bool `map:"Swarm-Act"` - HistoryAddress *swarm.Address `map:"Swarm-Act-History-Address"` + BatchID []byte `map:"Swarm-Postage-Batch-Id" validate:"required"` + SwarmTag uint64 `map:"Swarm-Tag"` + Act bool `map:"Swarm-Act"` + HistoryAddress swarm.Address `map:"Swarm-Act-History-Address"` }{} if response := s.mapStructure(r.Header, &headers); response != nil { response("invalid header params", logger, w) @@ -204,7 +204,7 @@ func (s *Service) chunkGetHandler(w http.ResponseWriter, r *http.Request) { } address := paths.Address - if v := getAddressFromContext(r.Context()); !v.Equal(swarm.ZeroAddress) { + if v := getAddressFromContext(r.Context()); !v.IsZero() { address = v } diff --git a/pkg/api/chunk_address.go b/pkg/api/chunk_address.go index c49c980c2e7..838c9ce6e8e 100644 --- a/pkg/api/chunk_address.go +++ b/pkg/api/chunk_address.go @@ -24,7 +24,7 @@ func (s *Service) hasChunkHandler(w http.ResponseWriter, r *http.Request) { } address := paths.Address - if v := getAddressFromContext(r.Context()); !v.Equal(swarm.ZeroAddress) { + if v := getAddressFromContext(r.Context()); !v.IsZero() { address = v } diff --git a/pkg/api/dirs.go b/pkg/api/dirs.go index 1ec5e6cde40..c85c68e4edb 100644 --- a/pkg/api/dirs.go +++ b/pkg/api/dirs.go @@ -48,7 +48,7 @@ func (s *Service) dirUploadHandler( tag uint64, rLevel redundancy.Level, act bool, - historyAddress *swarm.Address, + historyAddress swarm.Address, ) { if r.Body == http.NoBody { logger.Error(nil, "request has no body") diff --git a/pkg/api/dynamicaccess.go b/pkg/api/dynamicaccess.go index 7a05b1ad38c..c7c1279609e 100644 --- a/pkg/api/dynamicaccess.go +++ b/pkg/api/dynamicaccess.go @@ -28,6 +28,8 @@ func setAddressInContext(ctx context.Context, address swarm.Address) context.Con return context.WithValue(ctx, addressKey{}, address) } +// actDecryptionHandler is a middleware that looks up and decrypts the given address, +// if the act headers are present func (s *Service) actDecryptionHandler() func(h http.Handler) http.Handler { return func(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -56,7 +58,7 @@ func (s *Service) actDecryptionHandler() func(h http.Handler) http.Handler { return } ctx := r.Context() - reference, err := s.dac.DownloadHandler(ctx, *headers.Timestamp, paths.Address, headers.Publisher, *headers.HistoryAddress) + reference, err := s.dac.DownloadHandler(ctx, paths.Address, headers.Publisher, *headers.HistoryAddress, *headers.Timestamp) if err != nil { jsonhttp.InternalServerError(w, errActDownload) return @@ -67,27 +69,38 @@ func (s *Service) actDecryptionHandler() func(h http.Handler) http.Handler { } -// TODO: is ctx needed in ctrl upload ? +// actEncryptionHandler is a middleware that encrypts the given address using the publisher's public key +// Uploads the encrypted reference, history and kvs to the store func (s *Service) actEncryptionHandler( ctx context.Context, logger log.Logger, w http.ResponseWriter, putter storer.PutterSession, reference swarm.Address, - historyAddress *swarm.Address, + historyRootHash swarm.Address, ) (swarm.Address, error) { publisherPublicKey := &s.publicKey - kvsReference, historyReference, encryptedReference, err := s.dac.UploadHandler(ctx, reference, publisherPublicKey, historyAddress) + storageReference, historyReference, encryptedReference, err := s.dac.UploadHandler(ctx, reference, publisherPublicKey, historyRootHash) if err != nil { logger.Debug("act failed to encrypt reference", "error", err) logger.Error(nil, "act failed to encrypt reference") return swarm.ZeroAddress, err } - err = putter.Done(historyReference) - if err != nil { - logger.Debug("done split history failed", "error", err) - logger.Error(nil, "done split history failed") - return swarm.ZeroAddress, err + // only need to upload history and kvs if a new history is created, + // meaning that the publsher uploaded to the history for the first time + if !historyReference.Equal(historyRootHash) { + err = putter.Done(storageReference) + if err != nil { + logger.Debug("done split keyvaluestore failed", "error", err) + logger.Error(nil, "done split keyvaluestore failed") + return swarm.ZeroAddress, err + } + err = putter.Done(historyReference) + if err != nil { + logger.Debug("done split history failed", "error", err) + logger.Error(nil, "done split history failed") + return swarm.ZeroAddress, err + } } err = putter.Done(encryptedReference) if err != nil { @@ -95,12 +108,6 @@ func (s *Service) actEncryptionHandler( logger.Error(nil, "done split encrypted reference failed") return swarm.ZeroAddress, err } - err = putter.Done(kvsReference) - if err != nil { - logger.Debug("done split kvs reference failed", "error", err) - logger.Error(nil, "done split kvs reference failed") - return swarm.ZeroAddress, err - } w.Header().Set(SwarmActHistoryAddressHeader, historyReference.String()) diff --git a/pkg/api/dynamicaccess_test.go b/pkg/api/dynamicaccess_test.go index 4657b1f8bbe..480714444e0 100644 --- a/pkg/api/dynamicaccess_test.go +++ b/pkg/api/dynamicaccess_test.go @@ -36,27 +36,27 @@ func prepareHistoryFixture(storer api.Storer) (dynamicaccess.History, swarm.Addr ctx := context.Background() ls := loadsave.New(storer.ChunkStore(), storer.Cache(), pipelineFactory(storer.Cache(), false, redundancy.NONE)) - h, _ := dynamicaccess.NewHistory(ls, nil) + h, _ := dynamicaccess.NewHistory(ls) testActRef1 := swarm.NewAddress([]byte("39a5ea87b141fe44aa609c3327ecd891")) firstTime := time.Date(1994, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() - h.Add(ctx, testActRef1, &firstTime) + h.Add(ctx, testActRef1, &firstTime, nil) testActRef2 := swarm.NewAddress([]byte("39a5ea87b141fe44aa609c3327ecd892")) secondTime := time.Date(2000, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() - h.Add(ctx, testActRef2, &secondTime) + h.Add(ctx, testActRef2, &secondTime, nil) testActRef3 := swarm.NewAddress([]byte("39a5ea87b141fe44aa609c3327ecd893")) thirdTime := time.Date(2015, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() - h.Add(ctx, testActRef3, &thirdTime) + h.Add(ctx, testActRef3, &thirdTime, nil) testActRef4 := swarm.NewAddress([]byte("39a5ea87b141fe44aa609c3327ecd894")) fourthTime := time.Date(2020, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() - h.Add(ctx, testActRef4, &fourthTime) + h.Add(ctx, testActRef4, &fourthTime, nil) testActRef5 := swarm.NewAddress([]byte("39a5ea87b141fe44aa609c3327ecd895")) fifthTime := time.Date(2030, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() - h.Add(ctx, testActRef5, &fifthTime) + h.Add(ctx, testActRef5, &fifthTime, nil) ref, _ := h.Store(ctx) return h, ref @@ -371,7 +371,7 @@ func TestDacInvalidPath(t *testing.T) { // [positive tests] 1., 2.: uploading a file w/ and w/o history address then downloading it and checking the data. // [negative test] 3. uploading a file then downloading it with a wrong history address. // [negative test] 4. uploading a file to a wrong history address. -// [negative test] 4. downloading a file to w/o history address. +// [negative test] 5. downloading a file to w/o history address. func TestDacHistory(t *testing.T) { t.Parallel() var ( @@ -552,7 +552,7 @@ func TestDacHistory(t *testing.T) { } // nolint:paralleltest,tparallel -// TestDacTimestamp doc. comment +// TestDacTimestamp // [positive test] 1.: uploading a file w/ ACT then download it w/ timestamp and check the data. // [negative test] 2.: try to download a file w/o timestamp. func TestDacTimestamp(t *testing.T) { @@ -637,7 +637,7 @@ func TestDacTimestamp(t *testing.T) { } // nolint:paralleltest,tparallel -// TestDacPublisher doc. comment +// TestDacPublisher // [positive test] 1.: uploading a file w/ ACT then download it w/ the publisher address and check the data. // [negative test] 2.: expect Bad request when the public key is invalid. // [negative test] 3.: try to download a file w/ an incorrect publisher address. diff --git a/pkg/api/feed.go b/pkg/api/feed.go index eed03febe31..a2679547478 100644 --- a/pkg/api/feed.go +++ b/pkg/api/feed.go @@ -137,11 +137,11 @@ func (s *Service) feedPostHandler(w http.ResponseWriter, r *http.Request) { } headers := struct { - BatchID []byte `map:"Swarm-Postage-Batch-Id" validate:"required"` - Pin bool `map:"Swarm-Pin"` - Deferred *bool `map:"Swarm-Deferred-Upload"` - Act bool `map:"Swarm-Act"` - HistoryAddress *swarm.Address `map:"Swarm-Act-History-Address"` + BatchID []byte `map:"Swarm-Postage-Batch-Id" validate:"required"` + Pin bool `map:"Swarm-Pin"` + Deferred *bool `map:"Swarm-Deferred-Upload"` + Act bool `map:"Swarm-Act"` + HistoryAddress swarm.Address `map:"Swarm-Act-History-Address"` }{} if response := s.mapStructure(r.Header, &headers); response != nil { response("invalid header params", logger, w) diff --git a/pkg/api/soc.go b/pkg/api/soc.go index b4ce98e91eb..c2f11d6e05c 100644 --- a/pkg/api/soc.go +++ b/pkg/api/soc.go @@ -43,10 +43,10 @@ func (s *Service) socUploadHandler(w http.ResponseWriter, r *http.Request) { } headers := struct { - BatchID []byte `map:"Swarm-Postage-Batch-Id" validate:"required"` - Pin bool `map:"Swarm-Pin"` - Act bool `map:"Swarm-Act"` - HistoryAddress *swarm.Address `map:"Swarm-Act-History-Address"` + BatchID []byte `map:"Swarm-Postage-Batch-Id" validate:"required"` + Pin bool `map:"Swarm-Pin"` + Act bool `map:"Swarm-Act"` + HistoryAddress swarm.Address `map:"Swarm-Act-History-Address"` }{} if response := s.mapStructure(r.Header, &headers); response != nil { response("invalid header params", logger, w) diff --git a/pkg/dynamicaccess/accesslogic.go b/pkg/dynamicaccess/accesslogic.go index be9e8b194f1..b3b808e2b74 100644 --- a/pkg/dynamicaccess/accesslogic.go +++ b/pkg/dynamicaccess/accesslogic.go @@ -149,8 +149,8 @@ func (al ActLogic) DecryptRef(ctx context.Context, storage kvs.KeyValueStore, en return swarm.NewAddress(ref), nil } -func NewLogic(S Session) ActLogic { +func NewLogic(s Session) ActLogic { return ActLogic{ - Session: S, + Session: s, } } diff --git a/pkg/dynamicaccess/controller.go b/pkg/dynamicaccess/controller.go index 87a24e707b2..fdef1c4971a 100644 --- a/pkg/dynamicaccess/controller.go +++ b/pkg/dynamicaccess/controller.go @@ -35,11 +35,14 @@ type GranteeManager interface { GetGrantees(ctx context.Context, rootHash swarm.Address) ([]*ecdsa.PublicKey, error) } -// TODO: ądd granteeList ref to history metadata to solve inconsistency +// TODO: add granteeList ref to history metadata to solve inconsistency type Controller interface { GranteeManager - DownloadHandler(ctx context.Context, timestamp int64, encryptedRef swarm.Address, publisher *ecdsa.PublicKey, historyRootHash swarm.Address) (swarm.Address, error) - UploadHandler(ctx context.Context, reference swarm.Address, publisher *ecdsa.PublicKey, historyRootHash *swarm.Address) (swarm.Address, swarm.Address, swarm.Address, error) + // DownloadHandler decrypts the encryptedRef using the lookupkey based on the history and timestamp. + DownloadHandler(ctx context.Context, encryptedRef swarm.Address, publisher *ecdsa.PublicKey, historyRootHash swarm.Address, timestamp int64) (swarm.Address, error) + // TODO: history encryption + // UploadHandler encrypts the reference and stores it in the history as the latest update. + UploadHandler(ctx context.Context, reference swarm.Address, publisher *ecdsa.PublicKey, historyRootHash swarm.Address) (swarm.Address, swarm.Address, swarm.Address, error) } type controller struct { @@ -55,57 +58,61 @@ var _ Controller = (*controller)(nil) func (c *controller) DownloadHandler( ctx context.Context, - timestamp int64, encryptedRef swarm.Address, publisher *ecdsa.PublicKey, historyRootHash swarm.Address, + timestamp int64, ) (swarm.Address, error) { ls := loadsave.New(c.getter, c.putter, requestPipelineFactory(ctx, c.putter, false, redundancy.NONE)) - history, err := NewHistory(ls, &historyRootHash) + history, err := NewHistoryReference(ls, historyRootHash) if err != nil { return swarm.ZeroAddress, err } - - kvsRef, err := history.Lookup(ctx, timestamp) + entry, err := history.Lookup(ctx, timestamp) + if err != nil { + return swarm.ZeroAddress, err + } + // TODO: hanlde granteelist ref in mtdt + kvs, err := kvs.NewReference(ls, entry.Reference()) if err != nil { return swarm.ZeroAddress, err } - kvs := kvs.New(ls, kvsRef) + return c.accessLogic.DecryptRef(ctx, kvs, encryptedRef, publisher) } -// TODO: review return params: how to get back history ref ? func (c *controller) UploadHandler( ctx context.Context, refrefence swarm.Address, publisher *ecdsa.PublicKey, - historyRootHash *swarm.Address, + historyRootHash swarm.Address, ) (swarm.Address, swarm.Address, swarm.Address, error) { ls := loadsave.New(c.getter, c.putter, requestPipelineFactory(ctx, c.putter, false, redundancy.NONE)) - history, err := NewHistory(ls, historyRootHash) - if err != nil { - return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err - } + historyRef := historyRootHash + var ( + storage kvs.KeyValueStore + storageRef swarm.Address + ) now := time.Now().Unix() - kvsRef, err := history.Lookup(ctx, now) - if err != nil { - return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err - } - kvs := kvs.New(ls, kvsRef) - historyRef := swarm.ZeroAddress - if historyRootHash != nil { - historyRef = *historyRootHash - } - if kvsRef.Equal(swarm.ZeroAddress) { - err = c.accessLogic.AddPublisher(ctx, kvs, publisher) + if historyRef.IsZero() { + history, err := NewHistory(ls) + if err != nil { + return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err + } + storage, err = kvs.New(ls) + if err != nil { + return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err + } + err = c.accessLogic.AddPublisher(ctx, storage, publisher) if err != nil { return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err } - kvsRef, err = kvs.Save(ctx) + storageRef, err = storage.Save(ctx) if err != nil { return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err } - err = history.Add(ctx, kvsRef, &now) + // TODO: pass granteelist ref as mtdt + err = history.Add(ctx, storageRef, &now, nil) if err != nil { return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err } @@ -113,9 +120,25 @@ func (c *controller) UploadHandler( if err != nil { return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err } + } else { + history, err := NewHistoryReference(ls, historyRef) + if err != nil { + return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err + } + // TODO: hanlde granteelist ref in mtdt + entry, err := history.Lookup(ctx, now) + storageRef = entry.Reference() + if err != nil { + return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err + } + storage, err = kvs.NewReference(ls, storageRef) + if err != nil { + return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err + } } - encryptedRef, err := c.accessLogic.EncryptRef(ctx, kvs, publisher, refrefence) - return kvsRef, historyRef, encryptedRef, err + + encryptedRef, err := c.accessLogic.EncryptRef(ctx, storage, publisher, refrefence) + return storageRef, historyRef, encryptedRef, err } func NewController(ctx context.Context, accessLogic ActLogic, getter storage.Getter, putter storage.Putter) Controller { diff --git a/pkg/dynamicaccess/controller_test.go b/pkg/dynamicaccess/controller_test.go index 0512e6481f9..a48d426466e 100644 --- a/pkg/dynamicaccess/controller_test.go +++ b/pkg/dynamicaccess/controller_test.go @@ -19,21 +19,21 @@ import ( var hashFunc = sha3.NewLegacyKeccak256 func getHistoryFixture(ctx context.Context, ls file.LoadSaver, al dynamicaccess.ActLogic, publisher *ecdsa.PublicKey) (swarm.Address, error) { - h, err := dynamicaccess.NewHistory(ls, nil) + h, err := dynamicaccess.NewHistory(ls) if err != nil { return swarm.ZeroAddress, nil } pk1 := getPrivKey(1) pk2 := getPrivKey(2) - kvs0 := kvs.New(ls, swarm.ZeroAddress) + kvs0, _ := kvs.New(ls) al.AddPublisher(ctx, kvs0, publisher) kvs0Ref, _ := kvs0.Save(ctx) - kvs1 := kvs.New(ls, swarm.ZeroAddress) + kvs1, _ := kvs.New(ls) al.AddGrantee(ctx, kvs1, publisher, &pk1.PublicKey, nil) al.AddPublisher(ctx, kvs1, publisher) kvs1Ref, _ := kvs1.Save(ctx) - kvs2 := kvs.New(ls, swarm.ZeroAddress) + kvs2, _ := kvs.New(ls) al.AddGrantee(ctx, kvs2, publisher, &pk2.PublicKey, nil) al.AddPublisher(ctx, kvs2, publisher) kvs2Ref, _ := kvs2.Save(ctx) @@ -41,9 +41,9 @@ func getHistoryFixture(ctx context.Context, ls file.LoadSaver, al dynamicaccess. secondTime := time.Date(2000, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() thirdTime := time.Date(2015, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() - h.Add(ctx, kvs0Ref, &thirdTime) - h.Add(ctx, kvs1Ref, &firstTime) - h.Add(ctx, kvs2Ref, &secondTime) + h.Add(ctx, kvs0Ref, &thirdTime, nil) + h.Add(ctx, kvs1Ref, &firstTime, nil) + h.Add(ctx, kvs2Ref, &secondTime, nil) return h.Store(ctx) } @@ -55,9 +55,9 @@ func TestController_NewUploadDownload(t *testing.T) { al := dynamicaccess.NewLogic(diffieHellman) c := dynamicaccess.NewController(ctx, al, mockStorer.ChunkStore(), mockStorer.Cache()) ref := swarm.RandAddress(t) - _, hRef, encryptedRef, err := c.UploadHandler(ctx, ref, &publisher.PublicKey, nil) + _, hRef, encryptedRef, err := c.UploadHandler(ctx, ref, &publisher.PublicKey, swarm.ZeroAddress) assert.NoError(t, err) - dref, err := c.DownloadHandler(ctx, time.Now().Unix(), encryptedRef, &publisher.PublicKey, hRef) + dref, err := c.DownloadHandler(ctx, encryptedRef, &publisher.PublicKey, hRef, time.Now().Unix()) assert.NoError(t, err) assert.Equal(t, ref, dref) } @@ -72,9 +72,9 @@ func TestController_ExistingUploadDownload(t *testing.T) { ref := swarm.RandAddress(t) hRef, err := getHistoryFixture(ctx, ls, al, &publisher.PublicKey) assert.NoError(t, err) - _, hRef, encryptedRef, err := c.UploadHandler(ctx, ref, &publisher.PublicKey, &hRef) + _, hRef, encryptedRef, err := c.UploadHandler(ctx, ref, &publisher.PublicKey, hRef) assert.NoError(t, err) - dref, err := c.DownloadHandler(ctx, time.Now().Unix(), encryptedRef, &publisher.PublicKey, hRef) + dref, err := c.DownloadHandler(ctx, encryptedRef, &publisher.PublicKey, hRef, time.Now().Unix()) assert.NoError(t, err) assert.Equal(t, ref, dref) } diff --git a/pkg/dynamicaccess/grantee.go b/pkg/dynamicaccess/grantee.go index 8499cfc4f04..6724b718e4f 100644 --- a/pkg/dynamicaccess/grantee.go +++ b/pkg/dynamicaccess/grantee.go @@ -7,7 +7,6 @@ import ( "fmt" "github.com/ethersphere/bee/v2/pkg/file" - "github.com/ethersphere/bee/v2/pkg/storer" "github.com/ethersphere/bee/v2/pkg/swarm" ) @@ -25,7 +24,6 @@ type GranteeList interface { type GranteeListStruct struct { grantees []byte loadSave file.LoadSaver - putter storer.PutterSession } var _ GranteeList = (*GranteeListStruct)(nil) @@ -83,12 +81,8 @@ func (g *GranteeListStruct) Save(ctx context.Context) (swarm.Address, error) { if err != nil { return swarm.ZeroAddress, fmt.Errorf("grantee save error: %w", err) } - address := swarm.NewAddress(refBytes) - err = g.putter.Done(address) - if err != nil { - return swarm.ZeroAddress, err - } - return address, nil + + return swarm.NewAddress(refBytes), nil } func (g *GranteeListStruct) Remove(keysToRemove []*ecdsa.PublicKey) error { @@ -112,16 +106,15 @@ func (g *GranteeListStruct) Remove(keysToRemove []*ecdsa.PublicKey) error { return nil } -func NewGranteeList(ls file.LoadSaver, putter storer.PutterSession, reference swarm.Address) GranteeList { - var ( - data []byte - err error - ) - if swarm.ZeroAddress.Equal(reference) || swarm.EmptyAddress.Equal(reference) { - data = []byte{} - } else { - data, err = ls.Load(context.Background(), reference.Bytes()) +func NewGranteeList(ls file.LoadSaver) GranteeList { + return &GranteeListStruct{ + grantees: []byte{}, + loadSave: ls, } +} + +func NewGranteeListReference(ls file.LoadSaver, reference swarm.Address) GranteeList { + data, err := ls.Load(context.Background(), reference.Bytes()) if err != nil { return nil } @@ -129,10 +122,5 @@ func NewGranteeList(ls file.LoadSaver, putter storer.PutterSession, reference sw return &GranteeListStruct{ grantees: data, loadSave: ls, - putter: putter, } } - -func (g *GranteeListStruct) Store() (swarm.Address, error) { - return swarm.EmptyAddress, nil -} diff --git a/pkg/dynamicaccess/grantee_test.go b/pkg/dynamicaccess/grantee_test.go index 5730911ad74..0578be28ff4 100644 --- a/pkg/dynamicaccess/grantee_test.go +++ b/pkg/dynamicaccess/grantee_test.go @@ -15,7 +15,6 @@ import ( "github.com/ethersphere/bee/v2/pkg/file/redundancy" "github.com/ethersphere/bee/v2/pkg/storage" mockstorer "github.com/ethersphere/bee/v2/pkg/storer/mock" - "github.com/ethersphere/bee/v2/pkg/swarm" "github.com/stretchr/testify/assert" ) @@ -42,8 +41,7 @@ func generateKeyListFixture() ([]*ecdsa.PublicKey, error) { } func TestGranteeAddGet(t *testing.T) { - putter := mockStorer.DirectUpload() - gl := dynamicaccess.NewGranteeList(createLs(), putter, swarm.ZeroAddress) + gl := dynamicaccess.NewGranteeList(createLs()) keys, err := generateKeyListFixture() if err != nil { t.Errorf("key generation error: %v", err) @@ -100,8 +98,7 @@ func TestGranteeAddGet(t *testing.T) { } func TestGranteeRemove(t *testing.T) { - putter := mockStorer.DirectUpload() - gl := dynamicaccess.NewGranteeList(createLs(), putter, swarm.ZeroAddress) + gl := dynamicaccess.NewGranteeList(createLs()) keys, err := generateKeyListFixture() if err != nil { t.Errorf("key generation error: %v", err) @@ -154,12 +151,12 @@ func TestGranteeSave(t *testing.T) { t.Errorf("key generation error: %v", err) } t.Run("Save empty grantee list return NO error", func(t *testing.T) { - gl := dynamicaccess.NewGranteeList(createLs(), mockStorer.DirectUpload(), swarm.ZeroAddress) + gl := dynamicaccess.NewGranteeList(createLs()) _, err := gl.Save(ctx) assert.NoError(t, err) }) t.Run("Save not empty grantee list return valid swarm address", func(t *testing.T) { - gl := dynamicaccess.NewGranteeList(createLs(), mockStorer.DirectUpload(), swarm.ZeroAddress) + gl := dynamicaccess.NewGranteeList(createLs()) err = gl.Add(keys) ref, err := gl.Save(ctx) assert.NoError(t, err) @@ -167,8 +164,7 @@ func TestGranteeSave(t *testing.T) { }) t.Run("Save grantee list with one item, no error, pre-save value exist", func(t *testing.T) { ls := createLs() - putter := mockStorer.DirectUpload() - gl1 := dynamicaccess.NewGranteeList(ls, putter, swarm.ZeroAddress) + gl1 := dynamicaccess.NewGranteeList(ls) err := gl1.Add(keys) assert.NoError(t, err) @@ -176,24 +172,22 @@ func TestGranteeSave(t *testing.T) { ref, err := gl1.Save(ctx) assert.NoError(t, err) - gl2 := dynamicaccess.NewGranteeList(ls, putter, ref) + gl2 := dynamicaccess.NewGranteeListReference(ls, ref) val := gl2.Get() assert.NoError(t, err) assert.Equal(t, keys, val) }) t.Run("Save grantee list and add one item, no error, after-save value exist", func(t *testing.T) { ls := createLs() - putter := mockStorer.DirectUpload() - gl1 := dynamicaccess.NewGranteeList(ls, putter, swarm.ZeroAddress) + gl1 := dynamicaccess.NewGranteeList(ls) err := gl1.Add(keys) assert.NoError(t, err) ref, err := gl1.Save(ctx) assert.NoError(t, err) - // New KVS - gl2 := dynamicaccess.NewGranteeList(ls, putter, ref) + gl2 := dynamicaccess.NewGranteeListReference(ls, ref) err = gl2.Add(keys) assert.NoError(t, err) diff --git a/pkg/dynamicaccess/history.go b/pkg/dynamicaccess/history.go index 85a62085f36..25193adf886 100644 --- a/pkg/dynamicaccess/history.go +++ b/pkg/dynamicaccess/history.go @@ -15,8 +15,8 @@ import ( ) type History interface { - Add(ctx context.Context, ref swarm.Address, timestamp *int64) error - Lookup(ctx context.Context, timestamp int64) (swarm.Address, error) + Add(ctx context.Context, ref swarm.Address, timestamp *int64, metadata *map[string]string) error + Lookup(ctx context.Context, timestamp int64) (manifest.Entry, error) Store(ctx context.Context) (swarm.Address, error) } @@ -29,15 +29,22 @@ type history struct { ls file.LoadSaver } -func NewHistory(ls file.LoadSaver, ref *swarm.Address) (*history, error) { - var err error - var m manifest.Interface +func NewHistory(ls file.LoadSaver) (History, error) { + m, err := manifest.NewDefaultManifest(ls, false) + if err != nil { + return nil, err + } - if ref != nil { - m, err = manifest.NewDefaultManifestReference(*ref, ls) - } else { - m, err = manifest.NewDefaultManifest(ls, false) + mm, ok := m.(*manifest.MantarayManifest) + if !ok { + return nil, fmt.Errorf("expected MantarayManifest, got %T", m) } + + return &history{manifest: mm, ls: ls}, nil +} + +func NewHistoryReference(ls file.LoadSaver, ref swarm.Address) (History, error) { + m, err := manifest.NewDefaultManifestReference(ref, ls) if err != nil { return nil, err } @@ -50,9 +57,11 @@ func NewHistory(ls file.LoadSaver, ref *swarm.Address) (*history, error) { return &history{manifest: mm, ls: ls}, nil } -func (h *history) Add(ctx context.Context, ref swarm.Address, timestamp *int64) error { - // Do we need any extra meta/act? - meta := map[string]string{} +func (h *history) Add(ctx context.Context, ref swarm.Address, timestamp *int64, metadata *map[string]string) error { + mtdt := map[string]string{} + if metadata != nil { + mtdt = *metadata + } // add timestamps transformed so that the latests timestamp becomes the smallest key var unixTime int64 if timestamp != nil { @@ -62,29 +71,29 @@ func (h *history) Add(ctx context.Context, ref swarm.Address, timestamp *int64) } key := strconv.FormatInt(math.MaxInt64-unixTime, 10) - return h.manifest.Add(ctx, key, manifest.NewEntry(ref, meta)) + return h.manifest.Add(ctx, key, manifest.NewEntry(ref, mtdt)) } // Lookup finds the entry for a path or returns error if not found -func (h *history) Lookup(ctx context.Context, timestamp int64) (swarm.Address, error) { +func (h *history) Lookup(ctx context.Context, timestamp int64) (manifest.Entry, error) { if timestamp <= 0 { - return swarm.ZeroAddress, errors.New("invalid timestamp") + return manifest.NewEntry(swarm.ZeroAddress, map[string]string{}), errors.New("invalid timestamp") } reversedTimestamp := math.MaxInt64 - timestamp - node, err := h.LookupNode(ctx, reversedTimestamp) + node, err := h.lookupNode(ctx, reversedTimestamp) if err != nil { - return swarm.ZeroAddress, err + return manifest.NewEntry(swarm.ZeroAddress, map[string]string{}), err } if node != nil { - return swarm.NewAddress(node.Entry()), nil + return manifest.NewEntry(swarm.NewAddress(node.Entry()), node.Metadata()), nil } - return swarm.ZeroAddress, nil + return manifest.NewEntry(swarm.ZeroAddress, map[string]string{}), nil } -func (h *history) LookupNode(ctx context.Context, searchedTimestamp int64) (*mantaray.Node, error) { +func (h *history) lookupNode(ctx context.Context, searchedTimestamp int64) (*mantaray.Node, error) { // before node's timestamp is the closest one that is less than or equal to the searched timestamp // for instance: 2030, 2020, 1994 -> search for 2021 -> before is 2020 var beforeNode *mantaray.Node diff --git a/pkg/dynamicaccess/history_test.go b/pkg/dynamicaccess/history_test.go index 4b353823b00..5c7f5670cf6 100644 --- a/pkg/dynamicaccess/history_test.go +++ b/pkg/dynamicaccess/history_test.go @@ -2,6 +2,7 @@ package dynamicaccess_test import ( "context" + "reflect" "testing" "time" @@ -16,14 +17,14 @@ import ( ) func TestHistoryAdd(t *testing.T) { - h, err := dynamicaccess.NewHistory(nil, nil) + h, err := dynamicaccess.NewHistory(nil) assert.NoError(t, err) addr := swarm.NewAddress([]byte("addr")) ctx := context.Background() - err = h.Add(ctx, addr, nil) + err = h.Add(ctx, addr, nil, nil) assert.NoError(t, err) } @@ -32,20 +33,22 @@ func TestSingleNodeHistoryLookup(t *testing.T) { ctx := context.Background() ls := loadsave.New(storer.ChunkStore(), storer.Cache(), pipelineFactory(storer.Cache(), false)) - h, err := dynamicaccess.NewHistory(ls, nil) + h, err := dynamicaccess.NewHistory(ls) assert.NoError(t, err) testActRef := swarm.RandAddress(t) - err = h.Add(ctx, testActRef, nil) + err = h.Add(ctx, testActRef, nil, nil) assert.NoError(t, err) _, err = h.Store(ctx) assert.NoError(t, err) searchedTime := time.Now().Unix() - actRef, err := h.Lookup(ctx, searchedTime) + entry, err := h.Lookup(ctx, searchedTime) + actRef := entry.Reference() assert.NoError(t, err) assert.True(t, actRef.Equal(testActRef)) + assert.Nil(t, entry.Metadata()) } func TestMultiNodeHistoryLookup(t *testing.T) { @@ -53,51 +56,64 @@ func TestMultiNodeHistoryLookup(t *testing.T) { ctx := context.Background() ls := loadsave.New(storer.ChunkStore(), storer.Cache(), pipelineFactory(storer.Cache(), false)) - h, _ := dynamicaccess.NewHistory(ls, nil) + h, _ := dynamicaccess.NewHistory(ls) testActRef1 := swarm.NewAddress([]byte("39a5ea87b141fe44aa609c3327ecd891")) firstTime := time.Date(1994, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() - h.Add(ctx, testActRef1, &firstTime) + mtdt1 := map[string]string{"firstTime": "1994-04-01"} + h.Add(ctx, testActRef1, &firstTime, &mtdt1) testActRef2 := swarm.NewAddress([]byte("39a5ea87b141fe44aa609c3327ecd892")) secondTime := time.Date(2000, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() - h.Add(ctx, testActRef2, &secondTime) + mtdt2 := map[string]string{"secondTime": "2000-04-01"} + h.Add(ctx, testActRef2, &secondTime, &mtdt2) testActRef3 := swarm.NewAddress([]byte("39a5ea87b141fe44aa609c3327ecd893")) thirdTime := time.Date(2015, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() - h.Add(ctx, testActRef3, &thirdTime) + mtdt3 := map[string]string{"thirdTime": "2015-04-01"} + h.Add(ctx, testActRef3, &thirdTime, &mtdt3) testActRef4 := swarm.NewAddress([]byte("39a5ea87b141fe44aa609c3327ecd894")) fourthTime := time.Date(2020, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() - h.Add(ctx, testActRef4, &fourthTime) + mtdt4 := map[string]string{"fourthTime": "2020-04-01"} + h.Add(ctx, testActRef4, &fourthTime, &mtdt4) testActRef5 := swarm.NewAddress([]byte("39a5ea87b141fe44aa609c3327ecd895")) fifthTime := time.Date(2030, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() - h.Add(ctx, testActRef5, &fifthTime) + mtdt5 := map[string]string{"fifthTime": "2030-04-01"} + h.Add(ctx, testActRef5, &fifthTime, &mtdt5) // latest searchedTime := time.Date(1980, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() - actRef, err := h.Lookup(ctx, searchedTime) + entry, err := h.Lookup(ctx, searchedTime) + actRef := entry.Reference() assert.NoError(t, err) assert.True(t, actRef.Equal(testActRef1)) + assert.True(t, reflect.DeepEqual(mtdt1, entry.Metadata())) // before first time searchedTime = time.Date(2021, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() - actRef, err = h.Lookup(ctx, searchedTime) + entry, err = h.Lookup(ctx, searchedTime) + actRef = entry.Reference() assert.NoError(t, err) assert.True(t, actRef.Equal(testActRef4)) + assert.True(t, reflect.DeepEqual(mtdt4, entry.Metadata())) // same time searchedTime = time.Date(2000, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() - actRef, err = h.Lookup(ctx, searchedTime) + entry, err = h.Lookup(ctx, searchedTime) + actRef = entry.Reference() assert.NoError(t, err) assert.True(t, actRef.Equal(testActRef2)) + assert.True(t, reflect.DeepEqual(mtdt2, entry.Metadata())) // after time searchedTime = time.Date(2045, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() - actRef, err = h.Lookup(ctx, searchedTime) + entry, err = h.Lookup(ctx, searchedTime) + actRef = entry.Reference() assert.NoError(t, err) assert.True(t, actRef.Equal(testActRef5)) + assert.True(t, reflect.DeepEqual(mtdt5, entry.Metadata())) } func TestHistoryStore(t *testing.T) { @@ -105,14 +121,23 @@ func TestHistoryStore(t *testing.T) { ctx := context.Background() ls := loadsave.New(storer.ChunkStore(), storer.Cache(), pipelineFactory(storer.Cache(), false)) - h, _ := dynamicaccess.NewHistory(ls, nil) + h1, _ := dynamicaccess.NewHistory(ls) testActRef1 := swarm.NewAddress([]byte("39a5ea87b141fe44aa609c3327ecd891")) firstTime := time.Date(1994, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() - h.Add(ctx, testActRef1, &firstTime) + mtdt1 := map[string]string{"firstTime": "1994-04-01"} + h1.Add(ctx, testActRef1, &firstTime, &mtdt1) - _, err := h.Store(ctx) + href1, err := h1.Store(ctx) assert.NoError(t, err) + + h2, err := dynamicaccess.NewHistoryReference(ls, href1) + assert.NoError(t, err) + + entry1, err := h2.Lookup(ctx, firstTime) + actRef1 := entry1.Reference() + assert.True(t, actRef1.Equal(testActRef1)) + assert.True(t, reflect.DeepEqual(mtdt1, entry1.Metadata())) } func pipelineFactory(s storage.Putter, encrypt bool) func() pipeline.Interface { diff --git a/pkg/dynamicaccess/mock/service.go b/pkg/dynamicaccess/mock/service.go index 2d19c3ed4e3..6f4d5f09e72 100644 --- a/pkg/dynamicaccess/mock/service.go +++ b/pkg/dynamicaccess/mock/service.go @@ -78,7 +78,7 @@ func WithPublisher(ref string) Option { }) } -func (m *mockDacService) DownloadHandler(ctx context.Context, timestamp int64, encryptedRef swarm.Address, publisher *ecdsa.PublicKey, historyRootHash swarm.Address) (swarm.Address, error) { +func (m *mockDacService) DownloadHandler(ctx context.Context, encryptedRef swarm.Address, publisher *ecdsa.PublicKey, historyRootHash swarm.Address, timestamp int64) (swarm.Address, error) { if m.acceptAll { return swarm.ParseHexAddress("36e6c1bbdfee6ac21485d5f970479fd1df458d36df9ef4e8179708ed46da557f") } @@ -93,14 +93,15 @@ func (m *mockDacService) DownloadHandler(ctx context.Context, timestamp int64, e if !exists { return swarm.ZeroAddress, fmt.Errorf("history not found") } - kvsRef, err := h.Lookup(ctx, timestamp) - if kvsRef.Equal(swarm.ZeroAddress) || err != nil { + entry, err := h.Lookup(ctx, timestamp) + kvsRef := entry.Reference() + if kvsRef.IsZero() || err != nil { return swarm.ZeroAddress, fmt.Errorf("kvs not found") } return m.refMap[encryptedRef.String()], nil } -func (m *mockDacService) UploadHandler(ctx context.Context, reference swarm.Address, publisher *ecdsa.PublicKey, historyRootHash *swarm.Address) (swarm.Address, swarm.Address, swarm.Address, error) { +func (m *mockDacService) UploadHandler(ctx context.Context, reference swarm.Address, publisher *ecdsa.PublicKey, historyRootHash swarm.Address) (swarm.Address, swarm.Address, swarm.Address, error) { historyRef, _ := swarm.ParseHexAddress("67bdf80a9bbea8eca9c8480e43fdceb485d2d74d5708e45144b8c4adacd13d9c") kvsRef, _ := swarm.ParseHexAddress("3339613565613837623134316665343461613630396333333237656364383934") if m.acceptAll { @@ -112,22 +113,25 @@ func (m *mockDacService) UploadHandler(ctx context.Context, reference swarm.Addr exists bool ) now := time.Now().Unix() - if historyRootHash != nil { - historyRef = *historyRootHash + if !historyRootHash.IsZero() { + historyRef = historyRootHash h, exists = m.historyMap[historyRef.String()] if !exists { return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, fmt.Errorf("history not found") } - kvsRef, _ = h.Lookup(ctx, now) + entry, _ := h.Lookup(ctx, now) + kvsRef := entry.Reference() + if kvsRef.IsZero() { + return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, fmt.Errorf("kvs not found") + } } else { - h, _ = dynamicaccess.NewHistory(m.ls, nil) - h.Add(ctx, kvsRef, &now) + h, _ = dynamicaccess.NewHistory(m.ls) + // TODO: pass granteelist ref as mtdt + h.Add(ctx, kvsRef, &now, nil) historyRef, _ = h.Store(ctx) m.historyMap[historyRef.String()] = h } - if kvsRef.Equal(swarm.ZeroAddress) { - return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, fmt.Errorf("kvs not found") - } + encryptedRef, _ := m.encrypter.Encrypt(reference.Bytes()) m.refMap[(hex.EncodeToString(encryptedRef))] = reference return kvsRef, historyRef, swarm.NewAddress(encryptedRef), nil diff --git a/pkg/dynamicaccess/service.go b/pkg/dynamicaccess/service.go index c87b74ee60c..b9cedbcf82b 100644 --- a/pkg/dynamicaccess/service.go +++ b/pkg/dynamicaccess/service.go @@ -9,8 +9,8 @@ import ( ) type Service interface { - DownloadHandler(ctx context.Context, timestamp int64, encryptedRef swarm.Address, publisher *ecdsa.PublicKey, historyRootHash swarm.Address) (swarm.Address, error) - UploadHandler(ctx context.Context, reference swarm.Address, publisher *ecdsa.PublicKey, historyRootHash *swarm.Address) (swarm.Address, swarm.Address, swarm.Address, error) + DownloadHandler(ctx context.Context, encryptedRef swarm.Address, publisher *ecdsa.PublicKey, historyRootHash swarm.Address, timestamp int64) (swarm.Address, error) + UploadHandler(ctx context.Context, reference swarm.Address, publisher *ecdsa.PublicKey, historyRootHash swarm.Address) (swarm.Address, swarm.Address, swarm.Address, error) io.Closer } @@ -19,11 +19,11 @@ type service struct { controller Controller } -func (s *service) DownloadHandler(ctx context.Context, timestamp int64, encryptedRef swarm.Address, publisher *ecdsa.PublicKey, historyRootHash swarm.Address) (swarm.Address, error) { - return s.controller.DownloadHandler(ctx, timestamp, encryptedRef, publisher, historyRootHash) +func (s *service) DownloadHandler(ctx context.Context, encryptedRef swarm.Address, publisher *ecdsa.PublicKey, historyRootHash swarm.Address, timestamp int64) (swarm.Address, error) { + return s.controller.DownloadHandler(ctx, encryptedRef, publisher, historyRootHash, timestamp) } -func (s *service) UploadHandler(ctx context.Context, reference swarm.Address, publisher *ecdsa.PublicKey, historyRootHash *swarm.Address) (swarm.Address, swarm.Address, swarm.Address, error) { +func (s *service) UploadHandler(ctx context.Context, reference swarm.Address, publisher *ecdsa.PublicKey, historyRootHash swarm.Address) (swarm.Address, swarm.Address, swarm.Address, error) { return s.controller.UploadHandler(ctx, reference, publisher, historyRootHash) } diff --git a/pkg/kvs/kvs.go b/pkg/kvs/kvs.go index 42a07da6738..5f2381a8b24 100644 --- a/pkg/kvs/kvs.go +++ b/pkg/kvs/kvs.go @@ -57,21 +57,24 @@ func (s *keyValueStore) Save(ctx context.Context) (swarm.Address, error) { return ref, nil } -func New(ls file.LoadSaver, rootHash swarm.Address) KeyValueStore { - var ( - manif manifest.Interface - err error - ) - if swarm.ZeroAddress.Equal(rootHash) || swarm.EmptyAddress.Equal(rootHash) { - manif, err = manifest.NewSimpleManifest(ls) - } else { - manif, err = manifest.NewSimpleManifestReference(rootHash, ls) - } +func New(ls file.LoadSaver) (KeyValueStore, error) { + manif, err := manifest.NewSimpleManifest(ls) if err != nil { - return nil + return nil, err } return &keyValueStore{ manifest: manif, + }, nil +} + +func NewReference(ls file.LoadSaver, rootHash swarm.Address) (KeyValueStore, error) { + manif, err := manifest.NewSimpleManifestReference(rootHash, ls) + if err != nil { + return nil, err } + + return &keyValueStore{ + manifest: manif, + }, nil } diff --git a/pkg/kvs/kvs_test.go b/pkg/kvs/kvs_test.go index 5c6f75e2379..462e092d532 100644 --- a/pkg/kvs/kvs_test.go +++ b/pkg/kvs/kvs_test.go @@ -38,7 +38,9 @@ func keyValuePair(t *testing.T) ([]byte, []byte) { func TestKvs(t *testing.T) { - s := kvs.New(createLs(), swarm.ZeroAddress) + s, err := kvs.New(createLs()) + assert.NoError(t, err) + key, val := keyValuePair(t) ctx := context.Background() @@ -122,12 +124,12 @@ func TestKvs_Save(t *testing.T) { key1, val1 := keyValuePair(t) key2, val2 := keyValuePair(t) t.Run("Save empty KVS return error", func(t *testing.T) { - s := kvs.New(createLs(), swarm.ZeroAddress) + s, _ := kvs.New(createLs()) _, err := s.Save(ctx) assert.Error(t, err) }) t.Run("Save not empty KVS return valid swarm address", func(t *testing.T) { - s := kvs.New(createLs(), swarm.ZeroAddress) + s, _ := kvs.New(createLs()) s.Put(ctx, key1, val1) ref, err := s.Save(ctx) assert.NoError(t, err) @@ -135,7 +137,7 @@ func TestKvs_Save(t *testing.T) { }) t.Run("Save KVS with one item, no error, pre-save value exist", func(t *testing.T) { ls := createLs() - s1 := kvs.New(ls, swarm.ZeroAddress) + s1, _ := kvs.New(ls) err := s1.Put(ctx, key1, val1) assert.NoError(t, err) @@ -143,7 +145,9 @@ func TestKvs_Save(t *testing.T) { ref, err := s1.Save(ctx) assert.NoError(t, err) - s2 := kvs.New(ls, ref) + s2, err := kvs.NewReference(ls, ref) + assert.NoError(t, err) + val, err := s2.Get(ctx, key1) assert.NoError(t, err) assert.Equal(t, val1, val) @@ -151,7 +155,7 @@ func TestKvs_Save(t *testing.T) { t.Run("Save KVS and add one item, no error, after-save value exist", func(t *testing.T) { ls := createLs() - kvs1 := kvs.New(ls, swarm.ZeroAddress) + kvs1, _ := kvs.New(ls) err := kvs1.Put(ctx, key1, val1) assert.NoError(t, err) @@ -159,7 +163,8 @@ func TestKvs_Save(t *testing.T) { assert.NoError(t, err) // New KVS - kvs2 := kvs.New(ls, ref) + kvs2, err := kvs.NewReference(ls, ref) + assert.NoError(t, err) err = kvs2.Put(ctx, key2, val2) assert.NoError(t, err) From 29a59547a602dd72d0893ac249c281b9b3b5fc0f Mon Sep 17 00:00:00 2001 From: Kexort Date: Mon, 13 May 2024 15:09:10 +0200 Subject: [PATCH 25/34] ACT grantee management (#37) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * implement grantee management * Add POST endpoint + fixes * Save grantees as pubkey list and fix remove error; CHG: act-handler logger names * Refactor: pass getter, putter to controller functions * Refactor: error handling in dynamicaccess; Read cache header only for download handlers * CHG: grantees ref is encrypted and added to history ref + tests * Fix nil pointer dereference panic * CHG: put actref in handlegrantees; Add: pin, tag,deferred headers * CHG: pass loadsave to handlers; check if history address is nil * FIX: re-init history so that it can be saved; only add publisher if histroy is zero * make act timestamp optional * fix revoke grantees * Fix: Act timestamp header nil check; Uploadhandler UT * Fix controller nil pointer deref --------- Co-authored-by: Bálint Ujvári --- pkg/api/api.go | 4 +- pkg/api/api_test.go | 2 +- pkg/api/bytes.go | 2 +- pkg/api/bzz.go | 2 +- pkg/api/chunk.go | 2 +- pkg/api/dirs.go | 2 +- pkg/api/dynamicaccess.go | 430 +++++++++++++++++- pkg/api/dynamicaccess_test.go | 137 ++++++ pkg/api/feed.go | 2 +- pkg/api/router.go | 15 + pkg/api/soc.go | 2 +- pkg/dynamicaccess/accesslogic.go | 16 +- pkg/dynamicaccess/controller.go | 270 +++++++---- pkg/dynamicaccess/controller_test.go | 219 +++++++-- pkg/dynamicaccess/grantee.go | 130 ++++-- pkg/dynamicaccess/grantee_test.go | 68 ++- .../mock/{service.go => controller.go} | 51 ++- pkg/dynamicaccess/service.go | 39 -- pkg/node/devnode.go | 6 +- pkg/node/node.go | 6 +- 20 files changed, 1113 insertions(+), 292 deletions(-) rename pkg/dynamicaccess/mock/{service.go => controller.go} (69%) delete mode 100644 pkg/dynamicaccess/service.go diff --git a/pkg/api/api.go b/pkg/api/api.go index 00373c28186..ec5f4a508ad 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -154,7 +154,7 @@ type Service struct { feedFactory feeds.Factory signer crypto.Signer post postage.Service - dac dynamicaccess.Service + dac dynamicaccess.Controller postageContract postagecontract.Interface probe *Probe metricsRegistry *prometheus.Registry @@ -253,7 +253,7 @@ type ExtraOptions struct { Pss pss.Interface FeedFactory feeds.Factory Post postage.Service - Dac dynamicaccess.Service + Dac dynamicaccess.Controller PostageContract postagecontract.Interface Staking staking.Contract Steward steward.Interface diff --git a/pkg/api/api_test.go b/pkg/api/api_test.go index b556d6b439f..8a6781f7f98 100644 --- a/pkg/api/api_test.go +++ b/pkg/api/api_test.go @@ -104,7 +104,7 @@ type testServerOptions struct { PostageContract postagecontract.Interface StakingContract staking.Contract Post postage.Service - Dac dynamicaccess.Service + Dac dynamicaccess.Controller Steward steward.Interface WsHeaders http.Header Authenticator auth.Authenticator diff --git a/pkg/api/bytes.go b/pkg/api/bytes.go index a84ec2936de..2afb1db99c0 100644 --- a/pkg/api/bytes.go +++ b/pkg/api/bytes.go @@ -118,7 +118,7 @@ func (s *Service) bytesUploadHandler(w http.ResponseWriter, r *http.Request) { encryptedReference := reference if headers.Act { - encryptedReference, err = s.actEncryptionHandler(r.Context(), logger, w, putter, reference, headers.HistoryAddress) + encryptedReference, err = s.actEncryptionHandler(r.Context(), w, putter, reference, headers.HistoryAddress) if err != nil { jsonhttp.InternalServerError(w, errActUpload) return diff --git a/pkg/api/bzz.go b/pkg/api/bzz.go index 6319624a17d..b49bc89baea 100644 --- a/pkg/api/bzz.go +++ b/pkg/api/bzz.go @@ -266,7 +266,7 @@ func (s *Service) fileUploadHandler( encryptedReference := manifestReference if act { - encryptedReference, err = s.actEncryptionHandler(r.Context(), logger, w, putter, manifestReference, historyAddress) + encryptedReference, err = s.actEncryptionHandler(r.Context(), w, putter, manifestReference, historyAddress) if err != nil { jsonhttp.InternalServerError(w, errActUpload) return diff --git a/pkg/api/chunk.go b/pkg/api/chunk.go index 496f7f8d306..21daa0d0f57 100644 --- a/pkg/api/chunk.go +++ b/pkg/api/chunk.go @@ -143,7 +143,7 @@ func (s *Service) chunkUploadHandler(w http.ResponseWriter, r *http.Request) { encryptedReference := chunk.Address() if headers.Act { - encryptedReference, err = s.actEncryptionHandler(r.Context(), logger, w, putter, chunk.Address(), headers.HistoryAddress) + encryptedReference, err = s.actEncryptionHandler(r.Context(), w, putter, chunk.Address(), headers.HistoryAddress) if err != nil { jsonhttp.InternalServerError(w, errActUpload) return diff --git a/pkg/api/dirs.go b/pkg/api/dirs.go index c85c68e4edb..f187fbde01e 100644 --- a/pkg/api/dirs.go +++ b/pkg/api/dirs.go @@ -102,7 +102,7 @@ func (s *Service) dirUploadHandler( encryptedReference := reference if act { - encryptedReference, err = s.actEncryptionHandler(r.Context(), logger, w, putter, reference, historyAddress) + encryptedReference, err = s.actEncryptionHandler(r.Context(), w, putter, reference, historyAddress) if err != nil { jsonhttp.InternalServerError(w, errActUpload) return diff --git a/pkg/api/dynamicaccess.go b/pkg/api/dynamicaccess.go index c7c1279609e..bbf3abbc69e 100644 --- a/pkg/api/dynamicaccess.go +++ b/pkg/api/dynamicaccess.go @@ -3,10 +3,20 @@ package api import ( "context" "crypto/ecdsa" + "encoding/hex" + "encoding/json" + "errors" + "io" "net/http" + "time" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/ethersphere/bee/v2/pkg/crypto" + "github.com/ethersphere/bee/v2/pkg/file/loadsave" + "github.com/ethersphere/bee/v2/pkg/file/redundancy" "github.com/ethersphere/bee/v2/pkg/jsonhttp" - "github.com/ethersphere/bee/v2/pkg/log" + "github.com/ethersphere/bee/v2/pkg/postage" + storage "github.com/ethersphere/bee/v2/pkg/storage" storer "github.com/ethersphere/bee/v2/pkg/storer" "github.com/ethersphere/bee/v2/pkg/swarm" "github.com/gorilla/mux" @@ -14,6 +24,8 @@ import ( type addressKey struct{} +const granteeListEncrypt = true + // getAddressFromContext is a helper function to extract the address from the context func getAddressFromContext(ctx context.Context) swarm.Address { v, ok := ctx.Value(addressKey{}).(swarm.Address) @@ -28,12 +40,36 @@ func setAddressInContext(ctx context.Context, address swarm.Address) context.Con return context.WithValue(ctx, addressKey{}, address) } +type GranteesPatchRequest struct { + Addlist []string `json:"add"` + Revokelist []string `json:"revoke"` +} + +type GranteesPatchResponse struct { + Reference swarm.Address `json:"ref"` + HistoryReference swarm.Address `json:"historyref"` +} + +type GranteesPostRequest struct { + GranteeList []string `json:"grantees"` +} + +type GranteesPostResponse struct { + Reference swarm.Address `json:"ref"` + HistoryReference swarm.Address `json:"historyref"` +} + +type GranteesPatch struct { + Addlist []*ecdsa.PublicKey + Revokelist []*ecdsa.PublicKey +} + // actDecryptionHandler is a middleware that looks up and decrypts the given address, // if the act headers are present func (s *Service) actDecryptionHandler() func(h http.Handler) http.Handler { return func(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - logger := s.logger.WithName("acthandler").Build() + logger := s.logger.WithName("act_decryption_handler").Build() paths := struct { Address swarm.Address `map:"address,resolve" validate:"required"` }{} @@ -46,6 +82,7 @@ func (s *Service) actDecryptionHandler() func(h http.Handler) http.Handler { Timestamp *int64 `map:"Swarm-Act-Timestamp"` Publisher *ecdsa.PublicKey `map:"Swarm-Act-Publisher"` HistoryAddress *swarm.Address `map:"Swarm-Act-History-Address"` + Cache *bool `map:"Swarm-Cache"` }{} if response := s.mapStructure(r.Header, &headers); response != nil { response("invalid header params", logger, w) @@ -53,12 +90,23 @@ func (s *Service) actDecryptionHandler() func(h http.Handler) http.Handler { } // Try to download the file wihtout decryption, if the act headers are not present - if headers.Publisher == nil || headers.Timestamp == nil || headers.HistoryAddress == nil { + if headers.Publisher == nil || headers.HistoryAddress == nil { h.ServeHTTP(w, r) return } + + timestamp := time.Now().Unix() + if headers.Timestamp != nil { + timestamp = *headers.Timestamp + } + + cache := true + if headers.Cache != nil { + cache = *headers.Cache + } ctx := r.Context() - reference, err := s.dac.DownloadHandler(ctx, paths.Address, headers.Publisher, *headers.HistoryAddress, *headers.Timestamp) + ls := loadsave.NewReadonly(s.storer.Download(cache)) + reference, err := s.dac.DownloadHandler(ctx, ls, paths.Address, headers.Publisher, *headers.HistoryAddress, timestamp) if err != nil { jsonhttp.InternalServerError(w, errActDownload) return @@ -73,14 +121,15 @@ func (s *Service) actDecryptionHandler() func(h http.Handler) http.Handler { // Uploads the encrypted reference, history and kvs to the store func (s *Service) actEncryptionHandler( ctx context.Context, - logger log.Logger, w http.ResponseWriter, putter storer.PutterSession, reference swarm.Address, historyRootHash swarm.Address, ) (swarm.Address, error) { + logger := s.logger.WithName("act_encryption_handler").Build() publisherPublicKey := &s.publicKey - storageReference, historyReference, encryptedReference, err := s.dac.UploadHandler(ctx, reference, publisherPublicKey, historyRootHash) + ls := loadsave.New(s.storer.ChunkStore(), s.storer.Cache(), requestPipelineFactory(ctx, putter, false, redundancy.NONE)) + storageReference, historyReference, encryptedReference, err := s.dac.UploadHandler(ctx, ls, reference, publisherPublicKey, historyRootHash) if err != nil { logger.Debug("act failed to encrypt reference", "error", err) logger.Error(nil, "act failed to encrypt reference") @@ -102,14 +151,371 @@ func (s *Service) actEncryptionHandler( return swarm.ZeroAddress, err } } - err = putter.Done(encryptedReference) + + w.Header().Set(SwarmActHistoryAddressHeader, historyReference.String()) + return encryptedReference, nil +} + +// actListGranteesHandler is a middleware that decrypts the given address and returns the list of grantees, +// only the publisher is authorized to access the list +func (s *Service) actListGranteesHandler(w http.ResponseWriter, r *http.Request) { + logger := s.logger.WithName("act_list_grantees_handler").Build() + paths := struct { + GranteesAddress swarm.Address `map:"address,resolve" validate:"required"` + }{} + if response := s.mapStructure(mux.Vars(r), &paths); response != nil { + response("invalid path params", logger, w) + return + } + + headers := struct { + Cache *bool `map:"Swarm-Cache"` + }{} + if response := s.mapStructure(r.Header, &headers); response != nil { + response("invalid header params", logger, w) + return + } + cache := true + if headers.Cache != nil { + cache = *headers.Cache + } + publisher := &s.publicKey + ls := loadsave.NewReadonly(s.storer.Download(cache)) + grantees, err := s.dac.GetGrantees(r.Context(), ls, publisher, paths.GranteesAddress) if err != nil { - logger.Debug("done split encrypted reference failed", "error", err) - logger.Error(nil, "done split encrypted reference failed") - return swarm.ZeroAddress, err + logger.Debug("could not get grantees", "error", err) + logger.Error(nil, "could not get grantees") + jsonhttp.NotFound(w, "granteelist not found") + return + } + granteeSlice := make([]string, len(grantees)) + for i, grantee := range grantees { + granteeSlice[i] = hex.EncodeToString(crypto.EncodeSecp256k1PublicKey(grantee)) } + jsonhttp.OK(w, granteeSlice) +} - w.Header().Set(SwarmActHistoryAddressHeader, historyReference.String()) +// TODO: actGrantRevokeHandler doc. +func (s *Service) actGrantRevokeHandler(w http.ResponseWriter, r *http.Request) { + logger := s.logger.WithName("act_grant_revoke_handler").Build() - return encryptedReference, nil + if r.Body == http.NoBody { + logger.Error(nil, "request has no body") + jsonhttp.BadRequest(w, errInvalidRequest) + return + } + + paths := struct { + GranteesAddress swarm.Address `map:"address,resolve" validate:"required"` + }{} + if response := s.mapStructure(mux.Vars(r), &paths); response != nil { + response("invalid path params", logger, w) + return + } + + headers := struct { + BatchID []byte `map:"Swarm-Postage-Batch-Id" validate:"required"` + SwarmTag uint64 `map:"Swarm-Tag"` + Pin bool `map:"Swarm-Pin"` + Deferred *bool `map:"Swarm-Deferred-Upload"` + HistoryAddress *swarm.Address `map:"Swarm-Act-History-Address" validate:"required"` + }{} + if response := s.mapStructure(r.Header, &headers); response != nil { + response("invalid header params", logger, w) + return + } + + historyAddress := swarm.ZeroAddress + if headers.HistoryAddress != nil { + historyAddress = *headers.HistoryAddress + } + + var ( + tag uint64 + err error + deferred = defaultUploadMethod(headers.Deferred) + ) + + if deferred || headers.Pin { + tag, err = s.getOrCreateSessionID(headers.SwarmTag) + if err != nil { + logger.Debug("get or create tag failed", "error", err) + logger.Error(nil, "get or create tag failed") + switch { + case errors.Is(err, storage.ErrNotFound): + jsonhttp.NotFound(w, "tag not found") + default: + jsonhttp.InternalServerError(w, "cannot get or create tag") + } + return + } + } + + body, err := io.ReadAll(r.Body) + if err != nil { + if jsonhttp.HandleBodyReadError(err, w) { + return + } + logger.Debug("read request body failed", "error", err) + logger.Error(nil, "read request body failed") + jsonhttp.InternalServerError(w, "cannot read request") + return + } + + gpr := GranteesPatchRequest{} + if len(body) > 0 { + err = json.Unmarshal(body, &gpr) + if err != nil { + logger.Debug("unmarshal body failed", "error", err) + logger.Error(nil, "unmarshal body failed") + jsonhttp.InternalServerError(w, "error unmarshaling request body") + return + } + } + + grantees := GranteesPatch{} + paresAddlist, err := parseKeys(gpr.Addlist) + if err != nil { + logger.Debug("add list key parse failed", "error", err) + logger.Error(nil, "add list key parse failed") + jsonhttp.InternalServerError(w, "error add list key parsing") + return + } + grantees.Addlist = append(grantees.Addlist, paresAddlist...) + + paresRevokelist, err := parseKeys(gpr.Revokelist) + if err != nil { + logger.Debug("revoke list key parse failed", "error", err) + logger.Error(nil, "revoke list key parse failed") + jsonhttp.InternalServerError(w, "error revoke list key parsing") + return + } + grantees.Revokelist = append(grantees.Revokelist, paresRevokelist...) + + ctx := r.Context() + putter, err := s.newStamperPutter(ctx, putterOptions{ + BatchID: headers.BatchID, + TagID: tag, + Pin: headers.Pin, + Deferred: deferred, + }) + if err != nil { + logger.Debug("putter failed", "error", err) + logger.Error(nil, "putter failed") + switch { + case errors.Is(err, errBatchUnusable) || errors.Is(err, postage.ErrNotUsable): + jsonhttp.UnprocessableEntity(w, "batch not usable yet or does not exist") + case errors.Is(err, postage.ErrNotFound): + jsonhttp.NotFound(w, "batch with id not found") + case errors.Is(err, errInvalidPostageBatch): + jsonhttp.BadRequest(w, "invalid batch id") + case errors.Is(err, errUnsupportedDevNodeOperation): + jsonhttp.BadRequest(w, errUnsupportedDevNodeOperation) + default: + jsonhttp.BadRequest(w, nil) + } + return + } + + granteeref := paths.GranteesAddress + publisher := &s.publicKey + ls := loadsave.New(s.storer.ChunkStore(), s.storer.Cache(), requestPipelineFactory(ctx, putter, false, redundancy.NONE)) + gls := loadsave.New(s.storer.ChunkStore(), s.storer.Cache(), requestPipelineFactory(ctx, putter, granteeListEncrypt, redundancy.NONE)) + granteeref, encryptedglref, historyref, actref, err := s.dac.HandleGrantees(ctx, ls, gls, granteeref, historyAddress, publisher, grantees.Addlist, grantees.Revokelist) + if err != nil { + logger.Debug("failed to update grantee list", "error", err) + logger.Error(nil, "failed to update grantee list") + jsonhttp.InternalServerError(w, "failed to update grantee list") + return + } + + err = putter.Done(actref) + if err != nil { + logger.Debug("done split act failed", "error", err) + logger.Error(nil, "done split act failed") + jsonhttp.InternalServerError(w, "done split act failed") + return + } + + err = putter.Done(historyref) + if err != nil { + logger.Debug("done split history failed", "error", err) + logger.Error(nil, "done split history failed") + jsonhttp.InternalServerError(w, "done split history failed") + return + } + + err = putter.Done(granteeref) + if err != nil { + logger.Debug("done split grantees failed", "error", err) + logger.Error(nil, "done split grantees failed") + jsonhttp.InternalServerError(w, "done split grantees failed") + return + } + + jsonhttp.OK(w, GranteesPatchResponse{ + Reference: encryptedglref, + HistoryReference: historyref, + }) +} + +// TODO: actCreateGranteesHandler doc. +func (s *Service) actCreateGranteesHandler(w http.ResponseWriter, r *http.Request) { + logger := s.logger.WithName("acthandler").Build() + + if r.Body == http.NoBody { + logger.Error(nil, "request has no body") + jsonhttp.BadRequest(w, errInvalidRequest) + return + } + + headers := struct { + BatchID []byte `map:"Swarm-Postage-Batch-Id" validate:"required"` + SwarmTag uint64 `map:"Swarm-Tag"` + Pin bool `map:"Swarm-Pin"` + Deferred *bool `map:"Swarm-Deferred-Upload"` + HistoryAddress *swarm.Address `map:"Swarm-Act-History-Address"` + }{} + if response := s.mapStructure(r.Header, &headers); response != nil { + response("invalid header params", logger, w) + return + } + + historyAddress := swarm.ZeroAddress + if headers.HistoryAddress != nil { + historyAddress = *headers.HistoryAddress + } + + var ( + tag uint64 + err error + deferred = defaultUploadMethod(headers.Deferred) + ) + + if deferred || headers.Pin { + tag, err = s.getOrCreateSessionID(headers.SwarmTag) + if err != nil { + logger.Debug("get or create tag failed", "error", err) + logger.Error(nil, "get or create tag failed") + switch { + case errors.Is(err, storage.ErrNotFound): + jsonhttp.NotFound(w, "tag not found") + default: + jsonhttp.InternalServerError(w, "cannot get or create tag") + } + return + } + } + + body, err := io.ReadAll(r.Body) + if err != nil { + if jsonhttp.HandleBodyReadError(err, w) { + return + } + logger.Debug("read request body failed", "error", err) + logger.Error(nil, "read request body failed") + jsonhttp.InternalServerError(w, "cannot read request") + return + } + + gpr := GranteesPostRequest{} + if len(body) > 0 { + err = json.Unmarshal(body, &gpr) + if err != nil { + logger.Debug("unmarshal body failed", "error", err) + logger.Error(nil, "unmarshal body failed") + jsonhttp.InternalServerError(w, "error unmarshaling request body") + return + } + } + + list, err := parseKeys(gpr.GranteeList) + if err != nil { + logger.Debug("create list key parse failed", "error", err) + logger.Error(nil, "create list key parse failed") + jsonhttp.InternalServerError(w, "error create list key parsing") + return + } + + ctx := r.Context() + putter, err := s.newStamperPutter(ctx, putterOptions{ + BatchID: headers.BatchID, + TagID: tag, + Pin: headers.Pin, + Deferred: deferred, + }) + if err != nil { + logger.Debug("putter failed", "error", err) + logger.Error(nil, "putter failed") + switch { + case errors.Is(err, errBatchUnusable) || errors.Is(err, postage.ErrNotUsable): + jsonhttp.UnprocessableEntity(w, "batch not usable yet or does not exist") + case errors.Is(err, postage.ErrNotFound): + jsonhttp.NotFound(w, "batch with id not found") + case errors.Is(err, errInvalidPostageBatch): + jsonhttp.BadRequest(w, "invalid batch id") + case errors.Is(err, errUnsupportedDevNodeOperation): + jsonhttp.BadRequest(w, errUnsupportedDevNodeOperation) + default: + jsonhttp.BadRequest(w, nil) + } + return + } + + publisher := &s.publicKey + ls := loadsave.New(s.storer.ChunkStore(), s.storer.Cache(), requestPipelineFactory(ctx, putter, false, redundancy.NONE)) + gls := loadsave.New(s.storer.ChunkStore(), s.storer.Cache(), requestPipelineFactory(ctx, putter, granteeListEncrypt, redundancy.NONE)) + granteeref, encryptedglref, historyref, actref, err := s.dac.HandleGrantees(ctx, ls, gls, swarm.ZeroAddress, historyAddress, publisher, list, nil) + if err != nil { + logger.Debug("failed to update grantee list", "error", err) + logger.Error(nil, "failed to update grantee list") + jsonhttp.InternalServerError(w, "failed to update grantee list") + return + } + + err = putter.Done(actref) + if err != nil { + logger.Debug("done split act failed", "error", err) + logger.Error(nil, "done split act failed") + jsonhttp.InternalServerError(w, "done split act failed") + return + } + + err = putter.Done(historyref) + if err != nil { + logger.Debug("done split history failed", "error", err) + logger.Error(nil, "done split history failed") + jsonhttp.InternalServerError(w, "done split history failed") + return + } + + err = putter.Done(granteeref) + if err != nil { + logger.Debug("done split grantees failed", "error", err) + logger.Error(nil, "done split grantees failed") + jsonhttp.InternalServerError(w, "done split grantees failed") + return + } + + jsonhttp.Created(w, GranteesPostResponse{ + Reference: encryptedglref, + HistoryReference: historyref, + }) +} + +func parseKeys(list []string) ([]*ecdsa.PublicKey, error) { + parsedList := make([]*ecdsa.PublicKey, 0, len(list)) + for _, g := range list { + h, err := hex.DecodeString(g) + if err != nil { + return []*ecdsa.PublicKey{}, err + } + k, err := btcec.ParsePubKey(h) + if err != nil { + return []*ecdsa.PublicKey{}, err + } + parsedList = append(parsedList, k.ToECDSA()) + } + + return parsedList, nil } diff --git a/pkg/api/dynamicaccess_test.go b/pkg/api/dynamicaccess_test.go index 480714444e0..a2d0cd6e4b2 100644 --- a/pkg/api/dynamicaccess_test.go +++ b/pkg/api/dynamicaccess_test.go @@ -62,6 +62,7 @@ func prepareHistoryFixture(storer api.Storer) (dynamicaccess.History, swarm.Addr return h, ref } +// TODO: test tag, pin, deferred, stamp // TODO: feed test // nolint:paralleltest,tparallel // TestDacWithoutActHeader [positive tests]: @@ -387,6 +388,7 @@ func TestDacHistory(t *testing.T) { fileName = "sample.html" now = time.Now().Unix() ) + fmt.Printf("bagoy now: %d\n", now) t.Run("empty-history-upload-then-download-and-check-data", func(t *testing.T) { client, _, _, _ := newTestServer(t, testServerOptions{ @@ -802,3 +804,138 @@ func TestDacPublisher(t *testing.T) { ) }) } + +func TestDacGrantees(t *testing.T) { + t.Parallel() + var ( + spk, _ = hex.DecodeString("a786dd84b61485de12146fd9c4c02d87e8fd95f0542765cb7fc3d2e428c0bcfa") + pk, _ = crypto.DecodeSecp256k1PrivateKey(spk) + storerMock = mockstorer.New() + h, fixtureHref = prepareHistoryFixture(storerMock) + logger = log.Noop + addr = swarm.RandAddress(t) + client, _, _, _ = newTestServer(t, testServerOptions{ + Storer: storerMock, + Logger: logger, + Post: mockpost.New(mockpost.WithAcceptAll()), + PublicKey: pk.PublicKey, + Dac: mockdac.New(mockdac.WithHistory(h, fixtureHref.String())), + }) + ) + t.Run("get-grantees", func(t *testing.T) { + var ( + publicKeyBytes = crypto.EncodeSecp256k1PublicKey(&pk.PublicKey) + publisher = hex.EncodeToString(publicKeyBytes) + ) + clientwihtpublisher, _, _, _ := newTestServer(t, testServerOptions{ + Storer: storerMock, + Logger: logger, + Post: mockpost.New(mockpost.WithAcceptAll()), + PublicKey: pk.PublicKey, + Dac: mockdac.New(mockdac.WithHistory(h, fixtureHref.String()), mockdac.WithPublisher(publisher)), + }) + expected := []string{ + "03d7660772cc3142f8a7a2dfac46ce34d12eac1718720cef0e3d94347902aa96a2", + "03c712a7e29bc792ac8d8ae49793d28d5bda27ed70f0d90697b2fb456c0a168bd2", + "032541acf966823bae26c2c16a7102e728ade3e2e29c11a8a17b29d8eb2bd19302", + } + jsonhttptest.Request(t, clientwihtpublisher, http.MethodGet, "/grantee/"+addr.String(), http.StatusOK, + jsonhttptest.WithExpectedJSONResponse(expected), + ) + }) + + t.Run("get-grantees-unauthorized", func(t *testing.T) { + jsonhttptest.Request(t, client, http.MethodGet, "/grantee/fc4e9fe978991257b897d987bc4ff13058b66ef45a53189a0b4fe84bb3346396", http.StatusNotFound, + jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{ + Message: "granteelist not found", + Code: http.StatusNotFound, + }), + ) + }) + t.Run("get-grantees-invalid-address", func(t *testing.T) { + jsonhttptest.Request(t, client, http.MethodGet, "/grantee/asd", http.StatusBadRequest, + jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{ + Code: http.StatusBadRequest, + Message: "invalid path params", + Reasons: []jsonhttp.Reason{ + { + Field: "address", + Error: api.HexInvalidByteError('s').Error(), + }, + }}), + ) + }) + t.Run("add-revoke-grantees", func(t *testing.T) { + body := api.GranteesPatchRequest{ + Addlist: []string{"02ab7473879005929d10ce7d4f626412dad9fe56b0a6622038931d26bd79abf0a4"}, + Revokelist: []string{"02ab7473879005929d10ce7d4f626412dad9fe56b0a6622038931d26bd79abf0a4"}, + } + jsonhttptest.Request(t, client, http.MethodPatch, "/grantee/"+addr.String(), http.StatusOK, + jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr), + jsonhttptest.WithRequestHeader(api.SwarmActHistoryAddressHeader, addr.String()), + jsonhttptest.WithJSONRequestBody(body), + ) + }) + t.Run("add-revoke-grantees-empty-body", func(t *testing.T) { + jsonhttptest.Request(t, client, http.MethodPatch, "/grantee/"+addr.String(), http.StatusBadRequest, + jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr), + jsonhttptest.WithRequestBody(bytes.NewReader(nil)), + jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{ + Message: "could not validate request", + Code: http.StatusBadRequest, + }), + ) + }) + t.Run("add-grantee-with-history", func(t *testing.T) { + body := api.GranteesPatchRequest{ + Addlist: []string{"02ab7473879005929d10ce7d4f626412dad9fe56b0a6622038931d26bd79abf0a4"}, + } + jsonhttptest.Request(t, client, http.MethodPatch, "/grantee/"+addr.String(), http.StatusOK, + jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr), + jsonhttptest.WithRequestHeader(api.SwarmActHistoryAddressHeader, addr.String()), + jsonhttptest.WithJSONRequestBody(body), + ) + }) + t.Run("add-grantee-without-history", func(t *testing.T) { + body := api.GranteesPatchRequest{ + Addlist: []string{"02ab7473879005929d10ce7d4f626412dad9fe56b0a6622038931d26bd79abf0a4"}, + } + jsonhttptest.Request(t, client, http.MethodPatch, "/grantee/"+addr.String(), http.StatusBadRequest, + jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr), + jsonhttptest.WithJSONRequestBody(body), + ) + + }) + t.Run("create-granteelist", func(t *testing.T) { + body := api.GranteesPostRequest{ + GranteeList: []string{ + "02ab7473879005929d10ce7d4f626412dad9fe56b0a6622038931d26bd79abf0a4", + "03d7660772cc3142f8a7a2dfac46ce34d12eac1718720cef0e3d94347902aa96a2", + }, + } + jsonhttptest.Request(t, client, http.MethodPost, "/grantee", http.StatusCreated, + jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr), + jsonhttptest.WithJSONRequestBody(body), + ) + }) + t.Run("create-granteelist-without-stamp", func(t *testing.T) { + body := api.GranteesPostRequest{ + GranteeList: []string{ + "03d7660772cc3142f8a7a2dfac46ce34d12eac1718720cef0e3d94347902aa96a2", + }, + } + jsonhttptest.Request(t, client, http.MethodPost, "/grantee", http.StatusBadRequest, + jsonhttptest.WithJSONRequestBody(body), + ) + }) + t.Run("create-granteelist-empty-body", func(t *testing.T) { + jsonhttptest.Request(t, client, http.MethodPost, "/grantee", http.StatusBadRequest, + jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr), + jsonhttptest.WithRequestBody(bytes.NewReader(nil)), + jsonhttptest.WithExpectedJSONResponse(jsonhttp.StatusResponse{ + Message: "could not validate request", + Code: http.StatusBadRequest, + }), + ) + }) +} diff --git a/pkg/api/feed.go b/pkg/api/feed.go index a2679547478..3d43d3d148e 100644 --- a/pkg/api/feed.go +++ b/pkg/api/feed.go @@ -249,7 +249,7 @@ func (s *Service) feedPostHandler(w http.ResponseWriter, r *http.Request) { // TODO: do we want to allow feed act upload/ download? encryptedReference := ref if headers.Act { - encryptedReference, err = s.actEncryptionHandler(r.Context(), logger, w, putter, ref, headers.HistoryAddress) + encryptedReference, err = s.actEncryptionHandler(r.Context(), w, putter, ref, headers.HistoryAddress) if err != nil { jsonhttp.InternalServerError(w, errActUpload) return diff --git a/pkg/api/router.go b/pkg/api/router.go index 0e9ed177377..609ffff573e 100644 --- a/pkg/api/router.go +++ b/pkg/api/router.go @@ -270,6 +270,21 @@ func (s *Service) mountAPI() { ), }) + handle("/grantee", jsonhttp.MethodHandler{ + "POST": web.ChainHandlers( + web.FinalHandlerFunc(s.actCreateGranteesHandler), + ), + }) + + handle("/grantee/{address}", jsonhttp.MethodHandler{ + "GET": web.ChainHandlers( + web.FinalHandlerFunc(s.actListGranteesHandler), + ), + "PATCH": web.ChainHandlers( + web.FinalHandlerFunc(s.actGrantRevokeHandler), + ), + }) + handle("/bzz/{address}", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { u := r.URL u.Path += "/" diff --git a/pkg/api/soc.go b/pkg/api/soc.go index c2f11d6e05c..29777066b10 100644 --- a/pkg/api/soc.go +++ b/pkg/api/soc.go @@ -159,7 +159,7 @@ func (s *Service) socUploadHandler(w http.ResponseWriter, r *http.Request) { encryptedReference := sch.Address() if headers.Act { - encryptedReference, err = s.actEncryptionHandler(r.Context(), logger, w, putter, sch.Address(), headers.HistoryAddress) + encryptedReference, err = s.actEncryptionHandler(r.Context(), w, putter, sch.Address(), headers.HistoryAddress) if err != nil { jsonhttp.InternalServerError(w, errActUpload) return diff --git a/pkg/dynamicaccess/accesslogic.go b/pkg/dynamicaccess/accesslogic.go index b3b808e2b74..33b8ba819ba 100644 --- a/pkg/dynamicaccess/accesslogic.go +++ b/pkg/dynamicaccess/accesslogic.go @@ -11,6 +11,8 @@ import ( ) var hashFunc = sha3.NewLegacyKeccak256 +var oneByteArray = []byte{1} +var zeroByteArray = []byte{0} // Read-only interface for the ACT type Decryptor interface { @@ -50,15 +52,20 @@ func (al ActLogic) EncryptRef(ctx context.Context, storage kvs.KeyValueStore, pu return swarm.ZeroAddress, err } refCipher := encryption.New(accessKey, 0, uint32(0), hashFunc) - encryptedRef, _ := refCipher.Encrypt(ref.Bytes()) + encryptedRef, err := refCipher.Encrypt(ref.Bytes()) + if err != nil { + return swarm.ZeroAddress, err + } return swarm.NewAddress(encryptedRef), nil } // Adds a new grantee to the ACT func (al ActLogic) AddGrantee(ctx context.Context, storage kvs.KeyValueStore, publisherPubKey, granteePubKey *ecdsa.PublicKey, accessKeyPointer *encryption.Key) error { - var accessKey encryption.Key - var err error // Declare the "err" variable + var ( + accessKey encryption.Key + err error + ) if accessKeyPointer == nil { // Get previously generated access key @@ -109,9 +116,6 @@ func (al *ActLogic) getAccessKey(ctx context.Context, storage kvs.KeyValueStore, return accessKeyDecryptionCipher.Decrypt(encryptedAK) } -var oneByteArray = []byte{1} -var zeroByteArray = []byte{0} - // Generate lookup key and access key decryption key for a given public key func (al *ActLogic) getKeys(publicKey *ecdsa.PublicKey) ([][]byte, error) { return al.Session.Key(publicKey, [][]byte{zeroByteArray, oneByteArray}) diff --git a/pkg/dynamicaccess/controller.go b/pkg/dynamicaccess/controller.go index fdef1c4971a..a2c9ad50d04 100644 --- a/pkg/dynamicaccess/controller.go +++ b/pkg/dynamicaccess/controller.go @@ -3,67 +3,50 @@ package dynamicaccess import ( "context" "crypto/ecdsa" + "io" "time" - "github.com/ethersphere/bee/v2/pkg/file/loadsave" + encryption "github.com/ethersphere/bee/v2/pkg/encryption" + "github.com/ethersphere/bee/v2/pkg/file" "github.com/ethersphere/bee/v2/pkg/file/pipeline" "github.com/ethersphere/bee/v2/pkg/file/pipeline/builder" "github.com/ethersphere/bee/v2/pkg/file/redundancy" "github.com/ethersphere/bee/v2/pkg/kvs" - kvsmock "github.com/ethersphere/bee/v2/pkg/kvs/mock" "github.com/ethersphere/bee/v2/pkg/storage" "github.com/ethersphere/bee/v2/pkg/swarm" ) type GranteeManager interface { - //PUT /grantees/{grantee} - //body: {publisher?, grantee root hash ,grantee} - Grant(ctx context.Context, granteesAddress swarm.Address, grantee *ecdsa.PublicKey) error - //DELETE /grantees/{grantee} - //body: {publisher?, grantee root hash , grantee} - Revoke(ctx context.Context, granteesAddress swarm.Address, grantee *ecdsa.PublicKey) error - //[ ] - //POST /grantees - //body: {publisher, historyRootHash} - Commit(ctx context.Context, granteesAddress swarm.Address, actRootHash swarm.Address, publisher *ecdsa.PublicKey) (swarm.Address, swarm.Address, error) - - //Post /grantees - //{publisher, addList, removeList} - HandleGrantees(ctx context.Context, rootHash swarm.Address, publisher *ecdsa.PublicKey, addList, removeList []*ecdsa.PublicKey) error - - //GET /grantees/{history root hash} - GetGrantees(ctx context.Context, rootHash swarm.Address) ([]*ecdsa.PublicKey, error) + // TODO: doc + HandleGrantees(ctx context.Context, ls file.LoadSaver, gls file.LoadSaver, granteeref swarm.Address, historyref swarm.Address, publisher *ecdsa.PublicKey, addList, removeList []*ecdsa.PublicKey) (swarm.Address, swarm.Address, swarm.Address, swarm.Address, error) + // GetGrantees returns the list of grantees for the given publisher. + // The list is accessible only by the publisher. + GetGrantees(ctx context.Context, ls file.LoadSaver, publisher *ecdsa.PublicKey, encryptedglref swarm.Address) ([]*ecdsa.PublicKey, error) } -// TODO: add granteeList ref to history metadata to solve inconsistency type Controller interface { GranteeManager // DownloadHandler decrypts the encryptedRef using the lookupkey based on the history and timestamp. - DownloadHandler(ctx context.Context, encryptedRef swarm.Address, publisher *ecdsa.PublicKey, historyRootHash swarm.Address, timestamp int64) (swarm.Address, error) - // TODO: history encryption + DownloadHandler(ctx context.Context, ls file.LoadSaver, encryptedRef swarm.Address, publisher *ecdsa.PublicKey, historyRootHash swarm.Address, timestamp int64) (swarm.Address, error) // UploadHandler encrypts the reference and stores it in the history as the latest update. - UploadHandler(ctx context.Context, reference swarm.Address, publisher *ecdsa.PublicKey, historyRootHash swarm.Address) (swarm.Address, swarm.Address, swarm.Address, error) + UploadHandler(ctx context.Context, ls file.LoadSaver, reference swarm.Address, publisher *ecdsa.PublicKey, historyRootHash swarm.Address) (swarm.Address, swarm.Address, swarm.Address, error) + io.Closer } type controller struct { accessLogic ActLogic - granteeList GranteeList - //[ ]: do we need to protect this with a mutex? - revokeFlag []swarm.Address - getter storage.Getter - putter storage.Putter } var _ Controller = (*controller)(nil) func (c *controller) DownloadHandler( ctx context.Context, + ls file.LoadSaver, encryptedRef swarm.Address, publisher *ecdsa.PublicKey, historyRootHash swarm.Address, timestamp int64, ) (swarm.Address, error) { - ls := loadsave.New(c.getter, c.putter, requestPipelineFactory(ctx, c.putter, false, redundancy.NONE)) history, err := NewHistoryReference(ls, historyRootHash) if err != nil { return swarm.ZeroAddress, err @@ -72,26 +55,25 @@ func (c *controller) DownloadHandler( if err != nil { return swarm.ZeroAddress, err } - // TODO: hanlde granteelist ref in mtdt - kvs, err := kvs.NewReference(ls, entry.Reference()) + act, err := kvs.NewReference(ls, entry.Reference()) if err != nil { return swarm.ZeroAddress, err } - return c.accessLogic.DecryptRef(ctx, kvs, encryptedRef, publisher) + return c.accessLogic.DecryptRef(ctx, act, encryptedRef, publisher) } func (c *controller) UploadHandler( ctx context.Context, + ls file.LoadSaver, refrefence swarm.Address, publisher *ecdsa.PublicKey, historyRootHash swarm.Address, ) (swarm.Address, swarm.Address, swarm.Address, error) { - ls := loadsave.New(c.getter, c.putter, requestPipelineFactory(ctx, c.putter, false, redundancy.NONE)) historyRef := historyRootHash var ( - storage kvs.KeyValueStore - storageRef swarm.Address + storage kvs.KeyValueStore + actRef swarm.Address ) now := time.Now().Unix() if historyRef.IsZero() { @@ -107,12 +89,11 @@ func (c *controller) UploadHandler( if err != nil { return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err } - storageRef, err = storage.Save(ctx) + actRef, err = storage.Save(ctx) if err != nil { return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err } - // TODO: pass granteelist ref as mtdt - err = history.Add(ctx, storageRef, &now, nil) + err = history.Add(ctx, actRef, &now, nil) if err != nil { return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err } @@ -125,103 +106,199 @@ func (c *controller) UploadHandler( if err != nil { return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err } - // TODO: hanlde granteelist ref in mtdt entry, err := history.Lookup(ctx, now) - storageRef = entry.Reference() + actRef = entry.Reference() if err != nil { return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err } - storage, err = kvs.NewReference(ls, storageRef) + storage, err = kvs.NewReference(ls, actRef) if err != nil { return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err } } encryptedRef, err := c.accessLogic.EncryptRef(ctx, storage, publisher, refrefence) - return storageRef, historyRef, encryptedRef, err + return actRef, historyRef, encryptedRef, err } -func NewController(ctx context.Context, accessLogic ActLogic, getter storage.Getter, putter storage.Putter) Controller { +func NewController(accessLogic ActLogic) Controller { return &controller{ - granteeList: nil, accessLogic: accessLogic, - getter: getter, - putter: putter, } } -func (c *controller) Grant(ctx context.Context, granteesAddress swarm.Address, grantee *ecdsa.PublicKey) error { - return c.granteeList.Add([]*ecdsa.PublicKey{grantee}) -} - -func (c *controller) Revoke(ctx context.Context, granteesAddress swarm.Address, grantee *ecdsa.PublicKey) error { - if !c.isRevokeFlagged(granteesAddress) { - c.setRevokeFlag(granteesAddress, true) +func (c *controller) HandleGrantees( + ctx context.Context, + ls file.LoadSaver, + gls file.LoadSaver, + encryptedglref swarm.Address, + historyref swarm.Address, + publisher *ecdsa.PublicKey, + addList []*ecdsa.PublicKey, + removeList []*ecdsa.PublicKey, +) (swarm.Address, swarm.Address, swarm.Address, swarm.Address, error) { + var ( + err error + h History + act kvs.KeyValueStore + granteeref swarm.Address + ) + if !historyref.IsZero() { + h, err = NewHistoryReference(ls, historyref) + if err != nil { + return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err + } + entry, err := h.Lookup(ctx, time.Now().Unix()) + if err != nil { + return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err + } + actref := entry.Reference() + act, err = kvs.NewReference(ls, actref) + if err != nil { + return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err + } + } else { + h, err = NewHistory(ls) + if err != nil { + return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err + } + // generate new access key and new act + act, err = kvs.New(ls) + if err != nil { + return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err + } + err = c.accessLogic.AddPublisher(ctx, act, publisher) + if err != nil { + return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err + } } - return c.granteeList.Remove([]*ecdsa.PublicKey{grantee}) -} -func (c *controller) Commit(ctx context.Context, granteesAddress swarm.Address, actRootHash swarm.Address, publisher *ecdsa.PublicKey) (swarm.Address, swarm.Address, error) { - var act kvs.KeyValueStore - if c.isRevokeFlagged(granteesAddress) { - act = kvsmock.New() - c.accessLogic.AddPublisher(ctx, act, publisher) + var gl GranteeList + if encryptedglref.IsZero() { + gl, err = NewGranteeList(gls) + if err != nil { + return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err + } } else { - act = kvsmock.NewReference(actRootHash) + granteeref, err = c.decryptRefForPublisher(publisher, encryptedglref) + if err != nil { + return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err + } + + gl, err = NewGranteeListReference(gls, granteeref) + if err != nil { + return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err + } + } + if len(addList) != 0 { + err = gl.Add(addList) + if err != nil { + return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err + } + } + if len(removeList) != 0 { + err = gl.Remove(removeList) + if err != nil { + return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err + } } - grantees := c.granteeList.Get() - for _, grantee := range grantees { - c.accessLogic.AddGrantee(ctx, act, publisher, grantee, nil) + var granteesToAdd []*ecdsa.PublicKey + if len(removeList) != 0 || encryptedglref.IsZero() { + // generate new access key and new act + act, err = kvs.New(ls) + if err != nil { + return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err + } + err = c.accessLogic.AddPublisher(ctx, act, publisher) + if err != nil { + return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err + } + granteesToAdd = gl.Get() + } else { + granteesToAdd = addList } - granteeref, err := c.granteeList.Save(ctx) - if err != nil { - return swarm.EmptyAddress, swarm.EmptyAddress, err + for _, grantee := range granteesToAdd { + err := c.accessLogic.AddGrantee(ctx, act, publisher, grantee, nil) + if err != nil { + return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err + } } actref, err := act.Save(ctx) if err != nil { - return swarm.EmptyAddress, swarm.EmptyAddress, err + return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err } - c.setRevokeFlag(granteesAddress, false) - return granteeref, actref, err -} + glref, err := gl.Save(ctx) + if err != nil { + return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err + } -func (c *controller) HandleGrantees(ctx context.Context, granteesAddress swarm.Address, publisher *ecdsa.PublicKey, addList, removeList []*ecdsa.PublicKey) error { - act := kvsmock.New() + eglref, err := c.encryptRefForPublisher(publisher, glref) + if err != nil { + return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err + } + // need to re-initialize history, because Lookup loads the forks causing the manifest save to skip the root node + if !historyref.IsZero() { + h, err = NewHistoryReference(ls, historyref) + if err != nil { + return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err + } + } - c.accessLogic.AddPublisher(ctx, act, publisher) - for _, grantee := range addList { - c.accessLogic.AddGrantee(ctx, act, publisher, grantee, nil) + mtdt := map[string]string{"encryptedglref": eglref.String()} + err = h.Add(ctx, actref, nil, &mtdt) + if err != nil { + return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err } - return nil + href, err := h.Store(ctx) + if err != nil { + return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err + } + + return glref, eglref, href, actref, nil } -func (c *controller) GetGrantees(ctx context.Context, granteeRootHash swarm.Address) ([]*ecdsa.PublicKey, error) { - return c.granteeList.Get(), nil +func (c *controller) GetGrantees(ctx context.Context, ls file.LoadSaver, publisher *ecdsa.PublicKey, encryptedglref swarm.Address) ([]*ecdsa.PublicKey, error) { + granteeRef, err := c.decryptRefForPublisher(publisher, encryptedglref) + if err != nil { + return nil, err + } + gl, err := NewGranteeListReference(ls, granteeRef) + if err != nil { + return nil, err + } + return gl.Get(), nil } -func (c *controller) isRevokeFlagged(granteeRootHash swarm.Address) bool { - for _, revoke := range c.revokeFlag { - if revoke.Equal(granteeRootHash) { - return true - } +func (c *controller) encryptRefForPublisher(publisherPubKey *ecdsa.PublicKey, ref swarm.Address) (swarm.Address, error) { + keys, err := c.accessLogic.Session.Key(publisherPubKey, [][]byte{oneByteArray}) + if err != nil { + return swarm.ZeroAddress, err + } + refCipher := encryption.New(keys[0], 0, uint32(0), hashFunc) + encryptedRef, err := refCipher.Encrypt(ref.Bytes()) + if err != nil { + return swarm.ZeroAddress, err } - return false + + return swarm.NewAddress(encryptedRef), nil } -func (c *controller) setRevokeFlag(granteeRootHash swarm.Address, set bool) { - if set { - c.revokeFlag = append(c.revokeFlag, granteeRootHash) - } else { - for i, revoke := range c.revokeFlag { - if revoke.Equal(granteeRootHash) { - c.revokeFlag = append(c.revokeFlag[:i], c.revokeFlag[i+1:]...) - } - } +func (c *controller) decryptRefForPublisher(publisherPubKey *ecdsa.PublicKey, encryptedRef swarm.Address) (swarm.Address, error) { + keys, err := c.accessLogic.Session.Key(publisherPubKey, [][]byte{oneByteArray}) + if err != nil { + return swarm.ZeroAddress, err + } + refCipher := encryption.New(keys[0], 0, uint32(0), hashFunc) + ref, err := refCipher.Decrypt(encryptedRef.Bytes()) + if err != nil { + return swarm.ZeroAddress, err } + + return swarm.NewAddress(ref), nil } func requestPipelineFactory(ctx context.Context, s storage.Putter, encrypt bool, rLevel redundancy.Level) func() pipeline.Interface { @@ -229,3 +306,8 @@ func requestPipelineFactory(ctx context.Context, s storage.Putter, encrypt bool, return builder.NewPipelineBuilder(ctx, s, encrypt, rLevel) } } + +// TODO: what to do in close ? +func (s *controller) Close() error { + return nil +} diff --git a/pkg/dynamicaccess/controller_test.go b/pkg/dynamicaccess/controller_test.go index a48d426466e..2b3065899a1 100644 --- a/pkg/dynamicaccess/controller_test.go +++ b/pkg/dynamicaccess/controller_test.go @@ -3,25 +3,25 @@ package dynamicaccess_test import ( "context" "crypto/ecdsa" - "encoding/hex" + "reflect" "testing" "time" "github.com/ethersphere/bee/v2/pkg/dynamicaccess" - "github.com/ethersphere/bee/v2/pkg/encryption" + encryption "github.com/ethersphere/bee/v2/pkg/encryption" "github.com/ethersphere/bee/v2/pkg/file" + "github.com/ethersphere/bee/v2/pkg/file/loadsave" + "github.com/ethersphere/bee/v2/pkg/file/redundancy" "github.com/ethersphere/bee/v2/pkg/kvs" "github.com/ethersphere/bee/v2/pkg/swarm" "github.com/stretchr/testify/assert" "golang.org/x/crypto/sha3" ) -var hashFunc = sha3.NewLegacyKeccak256 - func getHistoryFixture(ctx context.Context, ls file.LoadSaver, al dynamicaccess.ActLogic, publisher *ecdsa.PublicKey) (swarm.Address, error) { h, err := dynamicaccess.NewHistory(ls) if err != nil { - return swarm.ZeroAddress, nil + return swarm.ZeroAddress, err } pk1 := getPrivKey(1) pk2 := getPrivKey(2) @@ -30,12 +30,12 @@ func getHistoryFixture(ctx context.Context, ls file.LoadSaver, al dynamicaccess. al.AddPublisher(ctx, kvs0, publisher) kvs0Ref, _ := kvs0.Save(ctx) kvs1, _ := kvs.New(ls) - al.AddGrantee(ctx, kvs1, publisher, &pk1.PublicKey, nil) al.AddPublisher(ctx, kvs1, publisher) + al.AddGrantee(ctx, kvs1, publisher, &pk1.PublicKey, nil) kvs1Ref, _ := kvs1.Save(ctx) kvs2, _ := kvs.New(ls) - al.AddGrantee(ctx, kvs2, publisher, &pk2.PublicKey, nil) al.AddPublisher(ctx, kvs2, publisher) + al.AddGrantee(ctx, kvs2, publisher, &pk2.PublicKey, nil) kvs2Ref, _ := kvs2.Save(ctx) firstTime := time.Date(1994, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() secondTime := time.Date(2000, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() @@ -47,55 +47,206 @@ func getHistoryFixture(ctx context.Context, ls file.LoadSaver, al dynamicaccess. return h.Store(ctx) } -// TODO: separate up down test with fixture, now these just check if the flow works at all -func TestController_NewUploadDownload(t *testing.T) { +func TestController_UploadHandler(t *testing.T) { ctx := context.Background() - publisher := getPrivKey(1) + publisher := getPrivKey(0) + diffieHellman := dynamicaccess.NewDefaultSession(publisher) + al := dynamicaccess.NewLogic(diffieHellman) + c := dynamicaccess.NewController(al) + ls := createLs() + + t.Run("New upload", func(t *testing.T) { + ref := swarm.RandAddress(t) + _, hRef, encRef, err := c.UploadHandler(ctx, ls, ref, &publisher.PublicKey, swarm.ZeroAddress) + assert.NoError(t, err) + + h, _ := dynamicaccess.NewHistoryReference(ls, hRef) + entry, _ := h.Lookup(ctx, time.Now().Unix()) + actRef := entry.Reference() + act, _ := kvs.NewReference(ls, actRef) + expRef, err := al.EncryptRef(ctx, act, &publisher.PublicKey, ref) + + assert.NoError(t, err) + assert.Equal(t, encRef, expRef) + assert.NotEqual(t, hRef, swarm.ZeroAddress) + }) + + t.Run("Upload to same history", func(t *testing.T) { + ref := swarm.RandAddress(t) + _, hRef1, _, err := c.UploadHandler(ctx, ls, ref, &publisher.PublicKey, swarm.ZeroAddress) + assert.NoError(t, err) + _, hRef2, encRef, err := c.UploadHandler(ctx, ls, ref, &publisher.PublicKey, hRef1) + assert.NoError(t, err) + h, err := dynamicaccess.NewHistoryReference(ls, hRef2) + assert.NoError(t, err) + hRef2, err = h.Store(ctx) + assert.NoError(t, err) + assert.True(t, hRef1.Equal(hRef2)) + + h, _ = dynamicaccess.NewHistoryReference(ls, hRef2) + entry, _ := h.Lookup(ctx, time.Now().Unix()) + actRef := entry.Reference() + act, _ := kvs.NewReference(ls, actRef) + expRef, err := al.EncryptRef(ctx, act, &publisher.PublicKey, ref) + + assert.NoError(t, err) + assert.Equal(t, encRef, expRef) + assert.NotEqual(t, hRef2, swarm.ZeroAddress) + }) +} + +func TestController_PublisherDownload(t *testing.T) { + ctx := context.Background() + publisher := getPrivKey(0) diffieHellman := dynamicaccess.NewDefaultSession(publisher) al := dynamicaccess.NewLogic(diffieHellman) - c := dynamicaccess.NewController(ctx, al, mockStorer.ChunkStore(), mockStorer.Cache()) + c := dynamicaccess.NewController(al) + ls := createLs() ref := swarm.RandAddress(t) - _, hRef, encryptedRef, err := c.UploadHandler(ctx, ref, &publisher.PublicKey, swarm.ZeroAddress) + href, err := getHistoryFixture(ctx, ls, al, &publisher.PublicKey) + h, err := dynamicaccess.NewHistoryReference(ls, href) + entry, err := h.Lookup(ctx, time.Now().Unix()) + actRef := entry.Reference() + act, err := kvs.NewReference(ls, actRef) + encRef, err := al.EncryptRef(ctx, act, &publisher.PublicKey, ref) + assert.NoError(t, err) - dref, err := c.DownloadHandler(ctx, encryptedRef, &publisher.PublicKey, hRef, time.Now().Unix()) + dref, err := c.DownloadHandler(ctx, ls, encRef, &publisher.PublicKey, href, time.Now().Unix()) assert.NoError(t, err) assert.Equal(t, ref, dref) } -func TestController_ExistingUploadDownload(t *testing.T) { - ls := createLs() +func TestController_GranteeDownload(t *testing.T) { ctx := context.Background() publisher := getPrivKey(0) - diffieHellman := dynamicaccess.NewDefaultSession(publisher) + grantee := getPrivKey(2) + publisherDH := dynamicaccess.NewDefaultSession(publisher) + publisherAL := dynamicaccess.NewLogic(publisherDH) + + diffieHellman := dynamicaccess.NewDefaultSession(grantee) al := dynamicaccess.NewLogic(diffieHellman) - c := dynamicaccess.NewController(ctx, al, mockStorer.ChunkStore(), mockStorer.Cache()) + ls := createLs() + c := dynamicaccess.NewController(al) ref := swarm.RandAddress(t) - hRef, err := getHistoryFixture(ctx, ls, al, &publisher.PublicKey) - assert.NoError(t, err) - _, hRef, encryptedRef, err := c.UploadHandler(ctx, ref, &publisher.PublicKey, hRef) + href, err := getHistoryFixture(ctx, ls, publisherAL, &publisher.PublicKey) + h, err := dynamicaccess.NewHistoryReference(ls, href) + ts := time.Date(2001, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() + entry, err := h.Lookup(ctx, ts) + actRef := entry.Reference() + act, err := kvs.NewReference(ls, actRef) + encRef, err := publisherAL.EncryptRef(ctx, act, &publisher.PublicKey, ref) + assert.NoError(t, err) - dref, err := c.DownloadHandler(ctx, encryptedRef, &publisher.PublicKey, hRef, time.Now().Unix()) + dref, err := c.DownloadHandler(ctx, ls, encRef, &publisher.PublicKey, href, ts) assert.NoError(t, err) assert.Equal(t, ref, dref) } -func TestControllerGrant(t *testing.T) { -} +func TestController_HandleGrantees(t *testing.T) { + ctx := context.Background() + publisher := getPrivKey(1) + diffieHellman := dynamicaccess.NewDefaultSession(publisher) + al := dynamicaccess.NewLogic(diffieHellman) + keys, _ := al.Session.Key(&publisher.PublicKey, [][]byte{{1}}) + refCipher := encryption.New(keys[0], 0, uint32(0), sha3.NewLegacyKeccak256) + ls := createLs() + gls := loadsave.New(mockStorer.ChunkStore(), mockStorer.Cache(), requestPipelineFactory(context.Background(), mockStorer.Cache(), true, redundancy.NONE)) + c := dynamicaccess.NewController(al) + href, _ := getHistoryFixture(ctx, ls, al, &publisher.PublicKey) -func TestControllerRevoke(t *testing.T) { + grantee1 := getPrivKey(0) + grantee := getPrivKey(2) -} + t.Run("add to new list", func(t *testing.T) { + addList := []*ecdsa.PublicKey{&grantee.PublicKey} + granteeRef, _, _, _, err := c.HandleGrantees(ctx, ls, ls, swarm.ZeroAddress, swarm.ZeroAddress, &publisher.PublicKey, addList, nil) + assert.NoError(t, err) + + gl, err := dynamicaccess.NewGranteeListReference(ls, granteeRef) + + assert.NoError(t, err) + assert.Len(t, gl.Get(), 1) + }) + t.Run("add to existing list", func(t *testing.T) { + addList := []*ecdsa.PublicKey{&grantee.PublicKey} + granteeRef, eglref, _, _, err := c.HandleGrantees(ctx, ls, gls, swarm.ZeroAddress, href, &publisher.PublicKey, addList, nil) + assert.NoError(t, err) + + gl, err := dynamicaccess.NewGranteeListReference(ls, granteeRef) + + assert.NoError(t, err) + assert.Len(t, gl.Get(), 1) -func TestControllerCommit(t *testing.T) { + addList = []*ecdsa.PublicKey{&getPrivKey(0).PublicKey} + granteeRef, _, _, _, err = c.HandleGrantees(ctx, ls, ls, eglref, href, &publisher.PublicKey, addList, nil) + gl, err = dynamicaccess.NewGranteeListReference(ls, granteeRef) + assert.NoError(t, err) + assert.Len(t, gl.Get(), 2) + }) + t.Run("add and revoke", func(t *testing.T) { + addList := []*ecdsa.PublicKey{&grantee.PublicKey} + revokeList := []*ecdsa.PublicKey{&grantee1.PublicKey} + gl, _ := dynamicaccess.NewGranteeList(ls) + gl.Add([]*ecdsa.PublicKey{&publisher.PublicKey, &grantee1.PublicKey}) + granteeRef, err := gl.Save(ctx) + eglref, _ := refCipher.Encrypt(granteeRef.Bytes()) + granteeRef, _, _, _, err = c.HandleGrantees(ctx, ls, gls, swarm.NewAddress(eglref), href, &publisher.PublicKey, addList, revokeList) + gl, err = dynamicaccess.NewGranteeListReference(ls, granteeRef) + + assert.NoError(t, err) + assert.Len(t, gl.Get(), 2) + }) + + t.Run("add twice", func(t *testing.T) { + addList := []*ecdsa.PublicKey{&grantee.PublicKey, &grantee.PublicKey} + granteeRef, eglref, _, _, err := c.HandleGrantees(ctx, ls, gls, swarm.ZeroAddress, href, &publisher.PublicKey, addList, nil) + granteeRef, _, _, _, err = c.HandleGrantees(ctx, ls, ls, eglref, href, &publisher.PublicKey, addList, nil) + gl, err := dynamicaccess.NewGranteeListReference(createLs(), granteeRef) + + assert.NoError(t, err) + assert.Len(t, gl.Get(), 1) + }) + t.Run("revoke non-existing", func(t *testing.T) { + addList := []*ecdsa.PublicKey{&grantee.PublicKey} + granteeRef, _, _, _, err := c.HandleGrantees(ctx, ls, ls, swarm.ZeroAddress, href, &publisher.PublicKey, addList, nil) + gl, err := dynamicaccess.NewGranteeListReference(createLs(), granteeRef) + + assert.NoError(t, err) + assert.Len(t, gl.Get(), 1) + }) } -func prepareEncryptedChunkReference(ak []byte) (swarm.Address, swarm.Address) { - addr, _ := hex.DecodeString("f7b1a45b70ee91d3dbfd98a2a692387f24db7279a9c96c447409e9205cf265baef29bf6aa294264762e33f6a18318562c86383dd8bfea2cec14fae08a8039bf3") - e1 := encryption.New(ak, 0, uint32(0), hashFunc) - ech, err := e1.Encrypt(addr) - if err != nil { - return swarm.EmptyAddress, swarm.EmptyAddress - } - return swarm.NewAddress(ech), swarm.NewAddress(addr) +func TestController_GetGrantees(t *testing.T) { + ctx := context.Background() + publisher := getPrivKey(1) + caller := getPrivKey(0) + grantee := getPrivKey(2) + diffieHellman1 := dynamicaccess.NewDefaultSession(publisher) + diffieHellman2 := dynamicaccess.NewDefaultSession(caller) + al1 := dynamicaccess.NewLogic(diffieHellman1) + al2 := dynamicaccess.NewLogic(diffieHellman2) + ls := createLs() + gls := loadsave.New(mockStorer.ChunkStore(), mockStorer.Cache(), requestPipelineFactory(context.Background(), mockStorer.Cache(), true, redundancy.NONE)) + c1 := dynamicaccess.NewController(al1) + c2 := dynamicaccess.NewController(al2) + + t.Run("get by publisher", func(t *testing.T) { + addList := []*ecdsa.PublicKey{&grantee.PublicKey} + granteeRef, eglRef, _, _, err := c1.HandleGrantees(ctx, ls, gls, swarm.ZeroAddress, swarm.ZeroAddress, &publisher.PublicKey, addList, nil) + + grantees, err := c1.GetGrantees(ctx, ls, &publisher.PublicKey, eglRef) + assert.NoError(t, err) + assert.True(t, reflect.DeepEqual(grantees, addList)) + + gl, _ := dynamicaccess.NewGranteeListReference(ls, granteeRef) + assert.True(t, reflect.DeepEqual(gl.Get(), addList)) + }) + t.Run("get by non-publisher", func(t *testing.T) { + addList := []*ecdsa.PublicKey{&grantee.PublicKey} + _, eglRef, _, _, err := c1.HandleGrantees(ctx, ls, gls, swarm.ZeroAddress, swarm.ZeroAddress, &publisher.PublicKey, addList, nil) + grantees, err := c2.GetGrantees(ctx, ls, &publisher.PublicKey, eglRef) + assert.Error(t, err) + assert.Nil(t, grantees) + }) } diff --git a/pkg/dynamicaccess/grantee.go b/pkg/dynamicaccess/grantee.go index 6724b718e4f..02cd8099021 100644 --- a/pkg/dynamicaccess/grantee.go +++ b/pkg/dynamicaccess/grantee.go @@ -6,6 +6,7 @@ import ( "crypto/elliptic" "fmt" + "github.com/btcsuite/btcd/btcec/v2" "github.com/ethersphere/bee/v2/pkg/file" "github.com/ethersphere/bee/v2/pkg/swarm" ) @@ -14,70 +15,60 @@ const ( publicKeyLen = 65 ) +// GranteeList manages a list of public keys. type GranteeList interface { + // Add adds a list of public keys to the grantee list. It filters out duplicates. Add(addList []*ecdsa.PublicKey) error + // Remove removes a list of public keys from the grantee list, if there is any. Remove(removeList []*ecdsa.PublicKey) error + // Get simply returns the list of public keys. Get() []*ecdsa.PublicKey + // Save saves the grantee list to the underlying storage and returns the reference. Save(ctx context.Context) (swarm.Address, error) } type GranteeListStruct struct { - grantees []byte + grantees []*ecdsa.PublicKey loadSave file.LoadSaver } var _ GranteeList = (*GranteeListStruct)(nil) func (g *GranteeListStruct) Get() []*ecdsa.PublicKey { - return g.deserialize(g.grantees) -} - -func (g *GranteeListStruct) serialize(publicKeys []*ecdsa.PublicKey) []byte { - b := make([]byte, 0, len(publicKeys)*publicKeyLen) - for _, key := range publicKeys { - b = append(b, g.serializePublicKey(key)...) - } - return b -} - -func (g *GranteeListStruct) serializePublicKey(pub *ecdsa.PublicKey) []byte { - return elliptic.Marshal(pub.Curve, pub.X, pub.Y) -} - -func (g *GranteeListStruct) deserialize(data []byte) []*ecdsa.PublicKey { - if len(data) == 0 { - return nil - } - - p := make([]*ecdsa.PublicKey, 0, len(data)/publicKeyLen) - for i := 0; i < len(data); i += publicKeyLen { - pubKey := g.deserializeBytes(data[i : i+publicKeyLen]) - if pubKey == nil { - return nil - } - p = append(p, pubKey) - } - return p -} - -func (g *GranteeListStruct) deserializeBytes(data []byte) *ecdsa.PublicKey { - curve := elliptic.P256() - x, y := elliptic.Unmarshal(curve, data) - return &ecdsa.PublicKey{Curve: curve, X: x, Y: y} + return g.grantees } func (g *GranteeListStruct) Add(addList []*ecdsa.PublicKey) error { if len(addList) == 0 { return fmt.Errorf("no public key provided") } + filteredList := make([]*ecdsa.PublicKey, 0, len(addList)) + for _, addkey := range addList { + add := true + for _, granteekey := range g.grantees { + if granteekey.Equal(addkey) { + add = false + break + } + } + for _, filteredkey := range filteredList { + if filteredkey.Equal(addkey) { + add = false + break + } + } + if add { + filteredList = append(filteredList, addkey) + } + } + g.grantees = append(g.grantees, filteredList...) - data := g.serialize(addList) - g.grantees = append(g.grantees, data...) return nil } func (g *GranteeListStruct) Save(ctx context.Context) (swarm.Address, error) { - refBytes, err := g.loadSave.Save(ctx, g.grantees) + data := serialize(g.grantees) + refBytes, err := g.loadSave.Save(ctx, data) if err != nil { return swarm.ZeroAddress, fmt.Errorf("grantee save error: %w", err) } @@ -89,38 +80,77 @@ func (g *GranteeListStruct) Remove(keysToRemove []*ecdsa.PublicKey) error { if len(keysToRemove) == 0 { return fmt.Errorf("nothing to remove") } - grantees := g.deserialize(g.grantees) - if grantees == nil { + + if len(g.grantees) == 0 { return fmt.Errorf("no grantee found") } + grantees := g.grantees for _, remove := range keysToRemove { - for i, grantee := range grantees { - if grantee.Equal(remove) { + for i := 0; i < len(grantees); i++ { + if grantees[i].Equal(remove) { grantees[i] = grantees[len(grantees)-1] grantees = grantees[:len(grantees)-1] } } } - g.grantees = g.serialize(grantees) + g.grantees = grantees + return nil } -func NewGranteeList(ls file.LoadSaver) GranteeList { +func NewGranteeList(ls file.LoadSaver) (GranteeList, error) { return &GranteeListStruct{ - grantees: []byte{}, + grantees: []*ecdsa.PublicKey{}, loadSave: ls, - } + }, nil } -func NewGranteeListReference(ls file.LoadSaver, reference swarm.Address) GranteeList { +func NewGranteeListReference(ls file.LoadSaver, reference swarm.Address) (GranteeList, error) { data, err := ls.Load(context.Background(), reference.Bytes()) if err != nil { - return nil + return nil, err } + grantees := deserialize(data) return &GranteeListStruct{ - grantees: data, + grantees: grantees, loadSave: ls, + }, nil +} + +func serialize(publicKeys []*ecdsa.PublicKey) []byte { + b := make([]byte, 0, len(publicKeys)*publicKeyLen) + for _, key := range publicKeys { + b = append(b, serializePublicKey(key)...) + } + return b +} + +func serializePublicKey(pub *ecdsa.PublicKey) []byte { + return elliptic.Marshal(pub.Curve, pub.X, pub.Y) +} + +func deserialize(data []byte) []*ecdsa.PublicKey { + if len(data) == 0 { + return []*ecdsa.PublicKey{} + } + + p := make([]*ecdsa.PublicKey, 0, len(data)/publicKeyLen) + for i := 0; i < len(data); i += publicKeyLen { + pubKey := deserializeBytes(data[i : i+publicKeyLen]) + if pubKey == nil { + return []*ecdsa.PublicKey{} + } + p = append(p, pubKey) + } + return p +} + +func deserializeBytes(data []byte) *ecdsa.PublicKey { + key, err := btcec.ParsePubKey(data) + if err != nil { + return nil } + return key.ToECDSA() } diff --git a/pkg/dynamicaccess/grantee_test.go b/pkg/dynamicaccess/grantee_test.go index 0578be28ff4..c4ce58ac8aa 100644 --- a/pkg/dynamicaccess/grantee_test.go +++ b/pkg/dynamicaccess/grantee_test.go @@ -3,10 +3,10 @@ package dynamicaccess_test import ( "context" "crypto/ecdsa" - "crypto/elliptic" "crypto/rand" "testing" + "github.com/btcsuite/btcd/btcec/v2" "github.com/ethersphere/bee/v2/pkg/dynamicaccess" "github.com/ethersphere/bee/v2/pkg/file" "github.com/ethersphere/bee/v2/pkg/file/loadsave" @@ -15,6 +15,7 @@ import ( "github.com/ethersphere/bee/v2/pkg/file/redundancy" "github.com/ethersphere/bee/v2/pkg/storage" mockstorer "github.com/ethersphere/bee/v2/pkg/storer/mock" + "github.com/ethersphere/bee/v2/pkg/swarm" "github.com/stretchr/testify/assert" ) @@ -31,9 +32,9 @@ func createLs() file.LoadSaver { } func generateKeyListFixture() ([]*ecdsa.PublicKey, error) { - key1, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - key2, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - key3, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + key1, err := ecdsa.GenerateKey(btcec.S256(), rand.Reader) + key2, err := ecdsa.GenerateKey(btcec.S256(), rand.Reader) + key3, err := ecdsa.GenerateKey(btcec.S256(), rand.Reader) if err != nil { return nil, err } @@ -41,7 +42,7 @@ func generateKeyListFixture() ([]*ecdsa.PublicKey, error) { } func TestGranteeAddGet(t *testing.T) { - gl := dynamicaccess.NewGranteeList(createLs()) + gl, _ := dynamicaccess.NewGranteeList(createLs()) keys, err := generateKeyListFixture() if err != nil { t.Errorf("key generation error: %v", err) @@ -49,14 +50,15 @@ func TestGranteeAddGet(t *testing.T) { t.Run("Get empty grantee list should return error", func(t *testing.T) { val := gl.Get() - assert.Nil(t, val) + assert.Empty(t, val) }) t.Run("Get should return value equal to put value", func(t *testing.T) { var ( + keys2, _ = generateKeyListFixture() addList1 []*ecdsa.PublicKey = []*ecdsa.PublicKey{keys[0]} - addList2 []*ecdsa.PublicKey = []*ecdsa.PublicKey{keys[1], keys[0]} - addList3 []*ecdsa.PublicKey = keys + addList2 []*ecdsa.PublicKey = []*ecdsa.PublicKey{keys[1], keys[2]} + addList3 []*ecdsa.PublicKey = keys2 ) testCases := []struct { name string @@ -66,6 +68,10 @@ func TestGranteeAddGet(t *testing.T) { name: "Test list = 1", list: addList1, }, + { + name: "Test list = duplicate1", + list: addList1, + }, { name: "Test list = 2", list: addList2, @@ -88,7 +94,9 @@ func TestGranteeAddGet(t *testing.T) { assert.Error(t, err) } else { assert.NoError(t, err) - expList = append(expList, tc.list...) + if tc.name != "Test list = duplicate1" { + expList = append(expList, tc.list...) + } retVal := gl.Get() assert.Equal(t, expList, retVal) } @@ -98,7 +106,7 @@ func TestGranteeAddGet(t *testing.T) { } func TestGranteeRemove(t *testing.T) { - gl := dynamicaccess.NewGranteeList(createLs()) + gl, _ := dynamicaccess.NewGranteeList(createLs()) keys, err := generateKeyListFixture() if err != nil { t.Errorf("key generation error: %v", err) @@ -128,19 +136,19 @@ func TestGranteeRemove(t *testing.T) { err := gl.Remove(removeList2) assert.NoError(t, err) retVal := gl.Get() - assert.Nil(t, retVal) + assert.Empty(t, retVal) }) t.Run("Remove from empty grantee list should return error", func(t *testing.T) { err := gl.Remove(removeList1) assert.Error(t, err) retVal := gl.Get() - assert.Nil(t, retVal) + assert.Empty(t, retVal) }) t.Run("Remove empty remove list should return error", func(t *testing.T) { err := gl.Remove(nil) assert.Error(t, err) retVal := gl.Get() - assert.Nil(t, retVal) + assert.Empty(t, retVal) }) } @@ -150,13 +158,18 @@ func TestGranteeSave(t *testing.T) { if err != nil { t.Errorf("key generation error: %v", err) } + t.Run("Create grantee list with invalid reference, expect error", func(t *testing.T) { + gl, err := dynamicaccess.NewGranteeListReference(createLs(), swarm.RandAddress(t)) + assert.Error(t, err) + assert.Nil(t, gl) + }) t.Run("Save empty grantee list return NO error", func(t *testing.T) { - gl := dynamicaccess.NewGranteeList(createLs()) + gl, _ := dynamicaccess.NewGranteeList(createLs()) _, err := gl.Save(ctx) assert.NoError(t, err) }) t.Run("Save not empty grantee list return valid swarm address", func(t *testing.T) { - gl := dynamicaccess.NewGranteeList(createLs()) + gl, _ := dynamicaccess.NewGranteeList(createLs()) err = gl.Add(keys) ref, err := gl.Save(ctx) assert.NoError(t, err) @@ -164,7 +177,7 @@ func TestGranteeSave(t *testing.T) { }) t.Run("Save grantee list with one item, no error, pre-save value exist", func(t *testing.T) { ls := createLs() - gl1 := dynamicaccess.NewGranteeList(ls) + gl1, _ := dynamicaccess.NewGranteeList(ls) err := gl1.Add(keys) assert.NoError(t, err) @@ -172,26 +185,39 @@ func TestGranteeSave(t *testing.T) { ref, err := gl1.Save(ctx) assert.NoError(t, err) - gl2 := dynamicaccess.NewGranteeListReference(ls, ref) + gl2, _ := dynamicaccess.NewGranteeListReference(ls, ref) val := gl2.Get() assert.NoError(t, err) assert.Equal(t, keys, val) }) t.Run("Save grantee list and add one item, no error, after-save value exist", func(t *testing.T) { ls := createLs() + keys2, _ := generateKeyListFixture() - gl1 := dynamicaccess.NewGranteeList(ls) + gl1, _ := dynamicaccess.NewGranteeList(ls) err := gl1.Add(keys) assert.NoError(t, err) ref, err := gl1.Save(ctx) assert.NoError(t, err) - gl2 := dynamicaccess.NewGranteeListReference(ls, ref) - err = gl2.Add(keys) + gl2, _ := dynamicaccess.NewGranteeListReference(ls, ref) + err = gl2.Add(keys2) assert.NoError(t, err) val := gl2.Get() - assert.Equal(t, append(keys, keys...), val) + assert.Equal(t, append(keys, keys2...), val) }) } + +func TestGranteeRemoveTwo(t *testing.T) { + gl, _ := dynamicaccess.NewGranteeList(createLs()) + keys, err := generateKeyListFixture() + if err != nil { + t.Errorf("key generation error: %v", err) + } + err = gl.Add([]*ecdsa.PublicKey{keys[0]}) + err = gl.Add([]*ecdsa.PublicKey{keys[0]}) + err = gl.Remove([]*ecdsa.PublicKey{keys[0]}) + assert.NoError(t, err) +} diff --git a/pkg/dynamicaccess/mock/service.go b/pkg/dynamicaccess/mock/controller.go similarity index 69% rename from pkg/dynamicaccess/mock/service.go rename to pkg/dynamicaccess/mock/controller.go index 6f4d5f09e72..09cacb65145 100644 --- a/pkg/dynamicaccess/mock/service.go +++ b/pkg/dynamicaccess/mock/controller.go @@ -44,7 +44,7 @@ type Option interface { func (f optionFunc) apply(r *mockDacService) { f(r) } // New creates a new mock dynamicaccess service. -func New(o ...Option) dynamicaccess.Service { +func New(o ...Option) dynamicaccess.Controller { storer := mockstorer.New() m := &mockDacService{ historyMap: make(map[string]dynamicaccess.History), @@ -78,7 +78,7 @@ func WithPublisher(ref string) Option { }) } -func (m *mockDacService) DownloadHandler(ctx context.Context, encryptedRef swarm.Address, publisher *ecdsa.PublicKey, historyRootHash swarm.Address, timestamp int64) (swarm.Address, error) { +func (m *mockDacService) DownloadHandler(ctx context.Context, ls file.LoadSaver, encryptedRef swarm.Address, publisher *ecdsa.PublicKey, historyRootHash swarm.Address, timestamp int64) (swarm.Address, error) { if m.acceptAll { return swarm.ParseHexAddress("36e6c1bbdfee6ac21485d5f970479fd1df458d36df9ef4e8179708ed46da557f") } @@ -101,7 +101,7 @@ func (m *mockDacService) DownloadHandler(ctx context.Context, encryptedRef swarm return m.refMap[encryptedRef.String()], nil } -func (m *mockDacService) UploadHandler(ctx context.Context, reference swarm.Address, publisher *ecdsa.PublicKey, historyRootHash swarm.Address) (swarm.Address, swarm.Address, swarm.Address, error) { +func (m *mockDacService) UploadHandler(ctx context.Context, ls file.LoadSaver, reference swarm.Address, publisher *ecdsa.PublicKey, historyRootHash swarm.Address) (swarm.Address, swarm.Address, swarm.Address, error) { historyRef, _ := swarm.ParseHexAddress("67bdf80a9bbea8eca9c8480e43fdceb485d2d74d5708e45144b8c4adacd13d9c") kvsRef, _ := swarm.ParseHexAddress("3339613565613837623134316665343461613630396333333237656364383934") if m.acceptAll { @@ -126,7 +126,6 @@ func (m *mockDacService) UploadHandler(ctx context.Context, reference swarm.Addr } } else { h, _ = dynamicaccess.NewHistory(m.ls) - // TODO: pass granteelist ref as mtdt h.Add(ctx, kvsRef, &now, nil) historyRef, _ = h.Store(ctx) m.historyMap[historyRef.String()] = h @@ -141,20 +140,38 @@ func (m *mockDacService) Close() error { return nil } -func (m *mockDacService) Grant(ctx context.Context, granteesAddress swarm.Address, grantee *ecdsa.PublicKey) error { - return nil -} -func (m *mockDacService) Revoke(ctx context.Context, granteesAddress swarm.Address, grantee *ecdsa.PublicKey) error { - return nil -} -func (m *mockDacService) Commit(ctx context.Context, granteesAddress swarm.Address, actRootHash swarm.Address, publisher *ecdsa.PublicKey) (swarm.Address, swarm.Address, error) { - return swarm.ZeroAddress, swarm.ZeroAddress, nil -} -func (m *mockDacService) HandleGrantees(ctx context.Context, rootHash swarm.Address, publisher *ecdsa.PublicKey, addList, removeList []*ecdsa.PublicKey) error { - return nil +func (m *mockDacService) HandleGrantees(ctx context.Context, ls file.LoadSaver, gls file.LoadSaver, encryptedglref swarm.Address, historyref swarm.Address, publisher *ecdsa.PublicKey, addList []*ecdsa.PublicKey, removeList []*ecdsa.PublicKey) (swarm.Address, swarm.Address, swarm.Address, swarm.Address, error) { + historyRef, _ := swarm.ParseHexAddress("67bdf80a9bbea8eca9c8480e43fdceb485d2d74d5708e45144b8c4adacd13d9c") + glRef, _ := swarm.ParseHexAddress("3339613565613837623134316665343461613630396333333237656364383934") + eglRef, _ := swarm.ParseHexAddress("fc4e9fe978991257b897d987bc4ff13058b66ef45a53189a0b4fe84bb3346396") + actref, _ := swarm.ParseHexAddress("39a5ea87b141fe44aa609c3327ecd896c0e2122897f5f4bbacf74db1033c5559") + return glRef, eglRef, historyRef, actref, nil } -func (m *mockDacService) GetGrantees(ctx context.Context, rootHash swarm.Address) ([]*ecdsa.PublicKey, error) { - return nil, nil + +func (m *mockDacService) GetGrantees(ctx context.Context, ls file.LoadSaver, publisher *ecdsa.PublicKey, encryptedglref swarm.Address) ([]*ecdsa.PublicKey, error) { + if m.publisher == "" { + return nil, fmt.Errorf("granteelist not found") + } + keys := []string{ + "a786dd84b61485de12146fd9c4c02d87e8fd95f0542765cb7fc3d2e428c0bcfa", + "b786dd84b61485de12146fd9c4c02d87e8fd95f0542765cb7fc3d2e428c0bcfb", + "c786dd84b61485de12146fd9c4c02d87e8fd95f0542765cb7fc3d2e428c0bcfc", + } + pubkeys := make([]*ecdsa.PublicKey, 0, len(keys)) + for i := range keys { + data, err := hex.DecodeString(keys[i]) + if err != nil { + panic(err) + } + + privKey, err := crypto.DecodeSecp256k1PrivateKey(data) + pubKey := privKey.PublicKey + if err != nil { + panic(err) + } + pubkeys = append(pubkeys, &pubKey) + } + return pubkeys, nil } func requestPipelineFactory(ctx context.Context, s storage.Putter, encrypt bool, rLevel redundancy.Level) func() pipeline.Interface { diff --git a/pkg/dynamicaccess/service.go b/pkg/dynamicaccess/service.go deleted file mode 100644 index b9cedbcf82b..00000000000 --- a/pkg/dynamicaccess/service.go +++ /dev/null @@ -1,39 +0,0 @@ -package dynamicaccess - -import ( - "context" - "crypto/ecdsa" - "io" - - "github.com/ethersphere/bee/v2/pkg/swarm" -) - -type Service interface { - DownloadHandler(ctx context.Context, encryptedRef swarm.Address, publisher *ecdsa.PublicKey, historyRootHash swarm.Address, timestamp int64) (swarm.Address, error) - UploadHandler(ctx context.Context, reference swarm.Address, publisher *ecdsa.PublicKey, historyRootHash swarm.Address) (swarm.Address, swarm.Address, swarm.Address, error) - io.Closer -} - -// TODO: is service needed at all? -> it is just a wrapper around controller -type service struct { - controller Controller -} - -func (s *service) DownloadHandler(ctx context.Context, encryptedRef swarm.Address, publisher *ecdsa.PublicKey, historyRootHash swarm.Address, timestamp int64) (swarm.Address, error) { - return s.controller.DownloadHandler(ctx, encryptedRef, publisher, historyRootHash, timestamp) -} - -func (s *service) UploadHandler(ctx context.Context, reference swarm.Address, publisher *ecdsa.PublicKey, historyRootHash swarm.Address) (swarm.Address, swarm.Address, swarm.Address, error) { - return s.controller.UploadHandler(ctx, reference, publisher, historyRootHash) -} - -// TODO: what to do in close ? -func (s *service) Close() error { - return nil -} - -func NewService(controller Controller) (Service, error) { - return &service{ - controller: controller, - }, nil -} diff --git a/pkg/node/devnode.go b/pkg/node/devnode.go index b7b35809f9c..389dc2337a5 100644 --- a/pkg/node/devnode.go +++ b/pkg/node/devnode.go @@ -238,11 +238,7 @@ func NewDevBee(logger log.Logger, o *DevOptions) (b *DevBee, err error) { session := dynamicaccess.NewDefaultSession(mockKey) actLogic := dynamicaccess.NewLogic(session) - ctrl := dynamicaccess.NewController(context.Background(), actLogic, localStore.ChunkStore(), localStore.Cache()) - dac, err := dynamicaccess.NewService(ctrl) - if err != nil { - return nil, fmt.Errorf("dac service: %w", err) - } + dac := dynamicaccess.NewController(actLogic) b.dacCloser = dac pssService := pss.New(mockKey, logger) diff --git a/pkg/node/node.go b/pkg/node/node.go index 8d2cdf5cff3..4ab881bc8ab 100644 --- a/pkg/node/node.go +++ b/pkg/node/node.go @@ -782,11 +782,7 @@ func NewBee( evictFn = func(id []byte) error { return localStore.EvictBatch(context.Background(), id) } actLogic := dynamicaccess.NewLogic(session) - ctrl := dynamicaccess.NewController(ctx, actLogic, localStore.ChunkStore(), localStore.Cache()) - dac, err := dynamicaccess.NewService(ctrl) - if err != nil { - return nil, fmt.Errorf("dac service: %w", err) - } + dac := dynamicaccess.NewController(actLogic) b.dacCloser = dac var ( From eb74fcf26995bc8c75ad0c5daa9af74177126fcd Mon Sep 17 00:00:00 2001 From: kopi-solarpunk <163832130+kopi-solarpunk@users.noreply.github.com> Date: Thu, 16 May 2024 10:03:28 +0200 Subject: [PATCH 26/34] Start refactoring for new linter rules (#39) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit refactor: refactoring to match new linter rules according to https://github.com/Solar-Punk-Ltd/bee/pull/38 Not everything is fixed, just a reference what can and needs to be improved. * implement grantee management * Add POST endpoint + fixes * Save grantees as pubkey list and fix remove error; CHG: act-handler logger names * Refactor: pass getter, putter to controller functions * Refactor: error handling in dynamicaccess; Read cache header only for download handlers * CHG: grantees ref is encrypted and added to history ref + tests * Fix nil pointer dereference panic * CHG: put actref in handlegrantees; Add: pin, tag,deferred headers * CHG: pass loadsave to handlers; check if history address is nil * FIX: re-init history so that it can be saved; only add publisher if histroy is zero * make act timestamp optional * fix revoke grantees * Fix: Act timestamp header nil check; Uploadhandler UT * refactor: start refactoring for now linter rules * refactor: revert non ACT related files * CHG: accesslogic getkeys refactor * refactor: fix errcheck and ineffassign linter errors in most cases * refactor: add headers, and change error handling * refactor: add headers --------- Co-authored-by: Kexort Co-authored-by: Bálint Ujvári --- pkg/api/dynamicaccess.go | 12 +++-- pkg/api/dynamicaccess_test.go | 48 +++++++++----------- pkg/dynamicaccess/accesslogic.go | 65 +++++++++++++++------------ pkg/dynamicaccess/accesslogic_test.go | 52 ++++++++------------- pkg/dynamicaccess/controller.go | 26 ++++++----- pkg/dynamicaccess/controller_test.go | 40 ++++++++++------- pkg/dynamicaccess/grantee.go | 20 ++++++--- pkg/dynamicaccess/grantee_test.go | 14 +++++- pkg/dynamicaccess/history.go | 42 ++++++++++------- pkg/dynamicaccess/history_test.go | 18 +++++--- pkg/dynamicaccess/mock/accesslogic.go | 3 ++ pkg/dynamicaccess/mock/controller.go | 4 +- pkg/dynamicaccess/mock/grantee.go | 4 ++ pkg/dynamicaccess/mock/session.go | 5 ++- pkg/dynamicaccess/session.go | 28 ++++++++---- pkg/dynamicaccess/session_test.go | 6 ++- pkg/kvs/kvs_test.go | 4 +- pkg/kvs/mock/kvs.go | 7 ++- pkg/manifest/mantaray/node.go | 7 +-- 19 files changed, 231 insertions(+), 174 deletions(-) diff --git a/pkg/api/dynamicaccess.go b/pkg/api/dynamicaccess.go index bbf3abbc69e..32447f905e8 100644 --- a/pkg/api/dynamicaccess.go +++ b/pkg/api/dynamicaccess.go @@ -1,3 +1,7 @@ +// Copyright 2024 The Swarm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package api import ( @@ -6,6 +10,7 @@ import ( "encoding/hex" "encoding/json" "errors" + "fmt" "io" "net/http" "time" @@ -114,7 +119,6 @@ func (s *Service) actDecryptionHandler() func(h http.Handler) http.Handler { h.ServeHTTP(w, r.WithContext(setAddressInContext(ctx, reference))) }) } - } // actEncryptionHandler is a middleware that encrypts the given address using the publisher's public key @@ -133,7 +137,7 @@ func (s *Service) actEncryptionHandler( if err != nil { logger.Debug("act failed to encrypt reference", "error", err) logger.Error(nil, "act failed to encrypt reference") - return swarm.ZeroAddress, err + return swarm.ZeroAddress, fmt.Errorf("act failed to encrypt reference: %w", err) } // only need to upload history and kvs if a new history is created, // meaning that the publsher uploaded to the history for the first time @@ -142,13 +146,13 @@ func (s *Service) actEncryptionHandler( if err != nil { logger.Debug("done split keyvaluestore failed", "error", err) logger.Error(nil, "done split keyvaluestore failed") - return swarm.ZeroAddress, err + return swarm.ZeroAddress, fmt.Errorf("done split keyvaluestore failed: %w", err) } err = putter.Done(historyReference) if err != nil { logger.Debug("done split history failed", "error", err) logger.Error(nil, "done split history failed") - return swarm.ZeroAddress, err + return swarm.ZeroAddress, fmt.Errorf("done split history failed: %w", err) } } diff --git a/pkg/api/dynamicaccess_test.go b/pkg/api/dynamicaccess_test.go index a2d0cd6e4b2..79eac36327b 100644 --- a/pkg/api/dynamicaccess_test.go +++ b/pkg/api/dynamicaccess_test.go @@ -40,23 +40,23 @@ func prepareHistoryFixture(storer api.Storer) (dynamicaccess.History, swarm.Addr testActRef1 := swarm.NewAddress([]byte("39a5ea87b141fe44aa609c3327ecd891")) firstTime := time.Date(1994, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() - h.Add(ctx, testActRef1, &firstTime, nil) + _ = h.Add(ctx, testActRef1, &firstTime, nil) testActRef2 := swarm.NewAddress([]byte("39a5ea87b141fe44aa609c3327ecd892")) secondTime := time.Date(2000, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() - h.Add(ctx, testActRef2, &secondTime, nil) + _ = h.Add(ctx, testActRef2, &secondTime, nil) testActRef3 := swarm.NewAddress([]byte("39a5ea87b141fe44aa609c3327ecd893")) thirdTime := time.Date(2015, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() - h.Add(ctx, testActRef3, &thirdTime, nil) + _ = h.Add(ctx, testActRef3, &thirdTime, nil) testActRef4 := swarm.NewAddress([]byte("39a5ea87b141fe44aa609c3327ecd894")) fourthTime := time.Date(2020, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() - h.Add(ctx, testActRef4, &fourthTime, nil) + _ = h.Add(ctx, testActRef4, &fourthTime, nil) testActRef5 := swarm.NewAddress([]byte("39a5ea87b141fe44aa609c3327ecd895")) fifthTime := time.Date(2030, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() - h.Add(ctx, testActRef5, &fifthTime, nil) + _ = h.Add(ctx, testActRef5, &fifthTime, nil) ref, _ := h.Store(ctx) return h, ref @@ -221,10 +221,11 @@ func TestDacEachEndpointWithAct(t *testing.T) { } } -// nolint:paralleltest,tparallel // TestDacWithoutActHeader [negative tests]: // 1. upload w/ "Swarm-Act" header then try to dowload w/o the header. // 2. upload w/o "Swarm-Act" header then try to dowload w/ the header. +// +//nolint:paralleltest,tparallel func TestDacWithoutAct(t *testing.T) { t.Parallel() var ( @@ -321,8 +322,9 @@ func TestDacWithoutAct(t *testing.T) { }) } -// nolint:paralleltest,tparallel // TestDacInvalidPath [negative test]: Expect Bad request when the path address is invalid. +// +//nolint:paralleltest,tparallel func TestDacInvalidPath(t *testing.T) { t.Parallel() var ( @@ -345,9 +347,7 @@ func TestDacInvalidPath(t *testing.T) { PublicKey: pk.PublicKey, Dac: mockdac.New(), }) - var ( - encryptedRef = "asd" - ) + encryptedRef := "asd" jsonhttptest.Request(t, client, http.MethodGet, fileDownloadResource(encryptedRef), http.StatusBadRequest, jsonhttptest.WithRequestHeader(api.SwarmActTimestampHeader, strconv.FormatInt(now, 10)), @@ -361,7 +361,8 @@ func TestDacInvalidPath(t *testing.T) { Field: "address", Error: api.HexInvalidByteError('s').Error(), }, - }}), + }, + }), jsonhttptest.WithRequestHeader(api.ContentTypeHeader, "text/html; charset=utf-8"), ) }) @@ -388,7 +389,6 @@ func TestDacHistory(t *testing.T) { fileName = "sample.html" now = time.Now().Unix() ) - fmt.Printf("bagoy now: %d\n", now) t.Run("empty-history-upload-then-download-and-check-data", func(t *testing.T) { client, _, _, _ := newTestServer(t, testServerOptions{ @@ -516,9 +516,7 @@ func TestDacHistory(t *testing.T) { PublicKey: pk.PublicKey, Dac: mockdac.New(), }) - var ( - testfile = "testfile1" - ) + testfile := "testfile1" jsonhttptest.Request(t, client, http.MethodPost, fileUploadResource+"?name="+fileName, http.StatusInternalServerError, jsonhttptest.WithRequestHeader(api.SwarmActHeader, "true"), @@ -541,9 +539,7 @@ func TestDacHistory(t *testing.T) { PublicKey: pk.PublicKey, Dac: mockdac.New(mockdac.WithHistory(h, fixtureHref.String())), }) - var ( - encryptedRef = "a5df670544eaea29e61b19d8739faa4573b19e4426e58a173e51ed0b5e7e2ade" - ) + encryptedRef := "a5df670544eaea29e61b19d8739faa4573b19e4426e58a173e51ed0b5e7e2ade" jsonhttptest.Request(t, client, http.MethodGet, fileDownloadResource(encryptedRef), http.StatusNotFound, jsonhttptest.WithRequestHeader(api.SwarmActTimestampHeader, strconv.FormatInt(now, 10)), @@ -619,9 +615,7 @@ func TestDacTimestamp(t *testing.T) { }) t.Run("download-w/o-timestamp", func(t *testing.T) { - var ( - encryptedRef = "a5df670544eaea29e61b19d8739faa4573b19e4426e58a173e51ed0b5e7e2ade" - ) + encryptedRef := "a5df670544eaea29e61b19d8739faa4573b19e4426e58a173e51ed0b5e7e2ade" client, _, _, _ := newTestServer(t, testServerOptions{ Storer: storerMock, Logger: logger, @@ -754,7 +748,8 @@ func TestDacPublisher(t *testing.T) { Field: "Swarm-Act-Publisher", Error: "malformed public key: invalid length: 32", }, - }}), + }, + }), jsonhttptest.WithRequestHeader(api.ContentTypeHeader, "text/html; charset=utf-8"), ) }) @@ -785,9 +780,7 @@ func TestDacPublisher(t *testing.T) { }) t.Run("download-w/o-publisher", func(t *testing.T) { - var ( - encryptedRef = "a5df670544eaea29e61b19d8739faa4573b19e4426e58a173e51ed0b5e7e2ade" - ) + encryptedRef := "a5df670544eaea29e61b19d8739faa4573b19e4426e58a173e51ed0b5e7e2ade" client, _, _, _ := newTestServer(t, testServerOptions{ Storer: storerMock, Logger: logger, @@ -862,7 +855,8 @@ func TestDacGrantees(t *testing.T) { Field: "address", Error: api.HexInvalidByteError('s').Error(), }, - }}), + }, + }), ) }) t.Run("add-revoke-grantees", func(t *testing.T) { @@ -904,8 +898,8 @@ func TestDacGrantees(t *testing.T) { jsonhttptest.WithRequestHeader(api.SwarmPostageBatchIdHeader, batchOkStr), jsonhttptest.WithJSONRequestBody(body), ) - }) + t.Run("create-granteelist", func(t *testing.T) { body := api.GranteesPostRequest{ GranteeList: []string{ diff --git a/pkg/dynamicaccess/accesslogic.go b/pkg/dynamicaccess/accesslogic.go index 33b8ba819ba..1cb9db8b5ba 100644 --- a/pkg/dynamicaccess/accesslogic.go +++ b/pkg/dynamicaccess/accesslogic.go @@ -1,8 +1,13 @@ +// Copyright 2024 The Swarm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package dynamicaccess import ( "context" "crypto/ecdsa" + "fmt" encryption "github.com/ethersphere/bee/v2/pkg/encryption" "github.com/ethersphere/bee/v2/pkg/kvs" @@ -10,11 +15,14 @@ import ( "golang.org/x/crypto/sha3" ) -var hashFunc = sha3.NewLegacyKeccak256 -var oneByteArray = []byte{1} -var zeroByteArray = []byte{0} +//nolint:gochecknoglobals +var ( + hashFunc = sha3.NewLegacyKeccak256 + oneByteArray = []byte{1} + zeroByteArray = []byte{0} +) -// Read-only interface for the ACT +// Decryptor is a read-only interface for the ACT. type Decryptor interface { // DecryptRef will return a decrypted reference, for given encrypted reference and grantee DecryptRef(ctx context.Context, storage kvs.KeyValueStore, encryptedRef swarm.Address, publisher *ecdsa.PublicKey) (swarm.Address, error) @@ -22,13 +30,13 @@ type Decryptor interface { Session } -// Control interface for the ACT (does write operations) +// Control interface for the ACT (does write operations). type Control interface { // Embedding the Decryptor interface Decryptor - // Adds a new grantee to the ACT + // AddGrantee adds a new grantee to the ACT AddGrantee(ctx context.Context, storage kvs.KeyValueStore, publisherPubKey, granteePubKey *ecdsa.PublicKey, accessKey *encryption.Key) error - // Encrypts a Swarm reference for a given grantee + // EncryptRef encrypts a Swarm reference for a given grantee EncryptRef(ctx context.Context, storage kvs.KeyValueStore, grantee *ecdsa.PublicKey, ref swarm.Address) (swarm.Address, error) } @@ -38,14 +46,14 @@ type ActLogic struct { var _ Control = (*ActLogic)(nil) -// Adds a new publisher to an empty act +// AddPublisher adds a new publisher to an empty act. func (al ActLogic) AddPublisher(ctx context.Context, storage kvs.KeyValueStore, publisher *ecdsa.PublicKey) error { accessKey := encryption.GenerateRandomKey(encryption.KeyLength) return al.AddGrantee(ctx, storage, publisher, publisher, &accessKey) } -// Encrypts a SWARM reference for a publisher +// EncryptRef encrypts a SWARM reference for a publisher. func (al ActLogic) EncryptRef(ctx context.Context, storage kvs.KeyValueStore, publisherPubKey *ecdsa.PublicKey, ref swarm.Address) (swarm.Address, error) { accessKey, err := al.getAccessKey(ctx, storage, publisherPubKey) if err != nil { @@ -54,13 +62,13 @@ func (al ActLogic) EncryptRef(ctx context.Context, storage kvs.KeyValueStore, pu refCipher := encryption.New(accessKey, 0, uint32(0), hashFunc) encryptedRef, err := refCipher.Encrypt(ref.Bytes()) if err != nil { - return swarm.ZeroAddress, err + return swarm.ZeroAddress, fmt.Errorf("failed to encrypt reference: %w", err) } return swarm.NewAddress(encryptedRef), nil } -// Adds a new grantee to the ACT +// AddGrantee adds a new grantee to the ACT. func (al ActLogic) AddGrantee(ctx context.Context, storage kvs.KeyValueStore, publisherPubKey, granteePubKey *ecdsa.PublicKey, accessKeyPointer *encryption.Key) error { var ( accessKey encryption.Key @@ -79,61 +87,60 @@ func (al ActLogic) AddGrantee(ctx context.Context, storage kvs.KeyValueStore, pu } // Encrypt the access key for the new Grantee - keys, err := al.getKeys(granteePubKey) + lookupKey, accessKeyDecryptionKey, err := al.getKeys(granteePubKey) if err != nil { return err } - lookupKey := keys[0] - // accessKeyDecryptionKey is used for encryption of the access key - accessKeyDecryptionKey := keys[1] // Encrypt the access key for the new Grantee cipher := encryption.New(encryption.Key(accessKeyDecryptionKey), 0, uint32(0), hashFunc) granteeEncryptedAccessKey, err := cipher.Encrypt(accessKey) if err != nil { - return err + return fmt.Errorf("failed to encrypt access key: %w", err) } - // Add the new encrypted access key for the Act + // Add the new encrypted access key to the Act return storage.Put(ctx, lookupKey, granteeEncryptedAccessKey) } -// Will return the access key for a publisher (public key) +// Will return the access key for a publisher (public key). func (al *ActLogic) getAccessKey(ctx context.Context, storage kvs.KeyValueStore, publisherPubKey *ecdsa.PublicKey) ([]byte, error) { - keys, err := al.getKeys(publisherPubKey) + publisherLookupKey, publisherAKDecryptionKey, err := al.getKeys(publisherPubKey) if err != nil { return nil, err } - publisherLookupKey := keys[0] - publisherAKDecryptionKey := keys[1] - // no need to constructor call if value not found in act + // no need for constructor call if value not found in act accessKeyDecryptionCipher := encryption.New(encryption.Key(publisherAKDecryptionKey), 0, uint32(0), hashFunc) encryptedAK, err := storage.Get(ctx, publisherLookupKey) if err != nil { - return nil, err + return nil, fmt.Errorf("failed go get value from KVS: %w", err) } return accessKeyDecryptionCipher.Decrypt(encryptedAK) } // Generate lookup key and access key decryption key for a given public key -func (al *ActLogic) getKeys(publicKey *ecdsa.PublicKey) ([][]byte, error) { - return al.Session.Key(publicKey, [][]byte{zeroByteArray, oneByteArray}) +func (al *ActLogic) getKeys(publicKey *ecdsa.PublicKey) ([]byte, []byte, error) { + nonces := [][]byte{zeroByteArray, oneByteArray} + // keys := make([][]byte, 0, len(nonces)) + keys, err := al.Session.Key(publicKey, nonces) + if keys == nil { + return nil, nil, err + } + return keys[0], keys[1], err } // DecryptRef will return a decrypted reference, for given encrypted reference and publisher func (al ActLogic) DecryptRef(ctx context.Context, storage kvs.KeyValueStore, encryptedRef swarm.Address, publisher *ecdsa.PublicKey) (swarm.Address, error) { - keys, err := al.getKeys(publisher) + lookupKey, accessKeyDecryptionKey, err := al.getKeys(publisher) if err != nil { return swarm.ZeroAddress, err } - lookupKey := keys[0] - accessKeyDecryptionKey := keys[1] // Lookup encrypted access key from the ACT manifest encryptedAccessKey, err := storage.Get(ctx, lookupKey) if err != nil { - return swarm.ZeroAddress, err + return swarm.ZeroAddress, fmt.Errorf("failed to get access key from KVS: %w", err) } // Decrypt access key diff --git a/pkg/dynamicaccess/accesslogic_test.go b/pkg/dynamicaccess/accesslogic_test.go index 66a3f8930e3..8d6c146e468 100644 --- a/pkg/dynamicaccess/accesslogic_test.go +++ b/pkg/dynamicaccess/accesslogic_test.go @@ -1,3 +1,7 @@ +// Copyright 2024 The Swarm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package dynamicaccess_test import ( @@ -12,6 +16,7 @@ import ( "github.com/ethersphere/bee/v2/pkg/dynamicaccess" kvsmock "github.com/ethersphere/bee/v2/pkg/kvs/mock" "github.com/ethersphere/bee/v2/pkg/swarm" + "github.com/stretchr/testify/assert" ) // Generates a new test environment with a fix private key @@ -79,7 +84,6 @@ func TestDecryptRef_Success(t *testing.T) { } if expectedRef.Compare(acutalRef) != 0 { - t.Errorf("Get gave back wrong Swarm reference!") } } @@ -123,7 +127,6 @@ func TestDecryptRefWithGrantee_Success(t *testing.T) { } if expectedRef.Compare(acutalRef) != 0 { - t.Errorf("Get gave back wrong Swarm reference!") } } @@ -135,9 +138,7 @@ func TestDecryptRef_Error(t *testing.T) { s := kvsmock.New() al := setupAccessLogic() err := al.AddPublisher(ctx, s, &id0.PublicKey) - if err != nil { - t.Errorf("AddPublisher: expected no error, got %v", err) - } + assert.NoError(t, err) expectedRef := "39a5ea87b141fe44aa609c3327ecd896c0e2122897f5f4bbacf74db1033c5559" @@ -158,19 +159,14 @@ func TestAddPublisher(t *testing.T) { al := setupAccessLogic() err := al.AddPublisher(ctx, s, &id0.PublicKey) - if err != nil { - t.Errorf("AddPublisher: expected no error, got %v", err) - } + assert.NoError(t, err) decodedSavedLookupKey, err := hex.DecodeString(savedLookupKey) - if err != nil { - t.Errorf("DecodeString: expected no error, got %v", err) - } + assert.NoError(t, err) encryptedAccessKey, err := s.Get(ctx, decodedSavedLookupKey) - if err != nil { - t.Errorf("Lookup: expected no error, got %v", err) - } + assert.NoError(t, err) + decodedEncryptedAccessKey := hex.EncodeToString(encryptedAccessKey) // A random value is returned so it is only possibly to check the length of the returned value @@ -184,7 +180,6 @@ func TestAddPublisher(t *testing.T) { } func TestAddNewGranteeToContent(t *testing.T) { - id0 := getPrivKey(0) id1 := getPrivKey(1) id2 := getPrivKey(2) @@ -197,24 +192,17 @@ func TestAddNewGranteeToContent(t *testing.T) { s := kvsmock.New() al := setupAccessLogic() err := al.AddPublisher(ctx, s, &id0.PublicKey) - if err != nil { - t.Errorf("AddNewGrantee: expected no error, got %v", err) - } + assert.NoError(t, err) err = al.AddGrantee(ctx, s, &id0.PublicKey, &id1.PublicKey, nil) - if err != nil { - t.Errorf("AddNewGrantee: expected no error, got %v", err) - } + assert.NoError(t, err) err = al.AddGrantee(ctx, s, &id0.PublicKey, &id2.PublicKey, nil) - if err != nil { - t.Errorf("AddNewGrantee: expected no error, got %v", err) - } + assert.NoError(t, err) lookupKeyAsByte, err := hex.DecodeString(publisherLookupKey) - if err != nil { - t.Errorf("AddNewGrantee: expected no error, got %v", err) - } + assert.NoError(t, err) + result, _ := s.Get(ctx, lookupKeyAsByte) hexEncodedEncryptedAK := hex.EncodeToString(result) if len(hexEncodedEncryptedAK) != 64 { @@ -222,9 +210,8 @@ func TestAddNewGranteeToContent(t *testing.T) { } lookupKeyAsByte, err = hex.DecodeString(firstAddedGranteeLookupKey) - if err != nil { - t.Errorf("AddNewGrantee: expected no error, got %v", err) - } + assert.NoError(t, err) + result, _ = s.Get(ctx, lookupKeyAsByte) hexEncodedEncryptedAK = hex.EncodeToString(result) if len(hexEncodedEncryptedAK) != 64 { @@ -232,9 +219,8 @@ func TestAddNewGranteeToContent(t *testing.T) { } lookupKeyAsByte, err = hex.DecodeString(secondAddedGranteeLookupKey) - if err != nil { - t.Errorf("AddNewGrantee: expected no error, got %v", err) - } + assert.NoError(t, err) + result, _ = s.Get(ctx, lookupKeyAsByte) hexEncodedEncryptedAK = hex.EncodeToString(result) if len(hexEncodedEncryptedAK) != 64 { diff --git a/pkg/dynamicaccess/controller.go b/pkg/dynamicaccess/controller.go index a2c9ad50d04..a8998aa12df 100644 --- a/pkg/dynamicaccess/controller.go +++ b/pkg/dynamicaccess/controller.go @@ -1,3 +1,7 @@ +// Copyright 2024 The Swarm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package dynamicaccess import ( @@ -33,13 +37,13 @@ type Controller interface { io.Closer } -type controller struct { +type ControllerStruct struct { accessLogic ActLogic } -var _ Controller = (*controller)(nil) +var _ Controller = (*ControllerStruct)(nil) -func (c *controller) DownloadHandler( +func (c *ControllerStruct) DownloadHandler( ctx context.Context, ls file.LoadSaver, encryptedRef swarm.Address, @@ -63,7 +67,7 @@ func (c *controller) DownloadHandler( return c.accessLogic.DecryptRef(ctx, act, encryptedRef, publisher) } -func (c *controller) UploadHandler( +func (c *ControllerStruct) UploadHandler( ctx context.Context, ls file.LoadSaver, refrefence swarm.Address, @@ -121,13 +125,13 @@ func (c *controller) UploadHandler( return actRef, historyRef, encryptedRef, err } -func NewController(accessLogic ActLogic) Controller { - return &controller{ +func NewController(accessLogic ActLogic) *ControllerStruct { + return &ControllerStruct{ accessLogic: accessLogic, } } -func (c *controller) HandleGrantees( +func (c *ControllerStruct) HandleGrantees( ctx context.Context, ls file.LoadSaver, gls file.LoadSaver, @@ -261,7 +265,7 @@ func (c *controller) HandleGrantees( return glref, eglref, href, actref, nil } -func (c *controller) GetGrantees(ctx context.Context, ls file.LoadSaver, publisher *ecdsa.PublicKey, encryptedglref swarm.Address) ([]*ecdsa.PublicKey, error) { +func (c *ControllerStruct) GetGrantees(ctx context.Context, ls file.LoadSaver, publisher *ecdsa.PublicKey, encryptedglref swarm.Address) ([]*ecdsa.PublicKey, error) { granteeRef, err := c.decryptRefForPublisher(publisher, encryptedglref) if err != nil { return nil, err @@ -273,7 +277,7 @@ func (c *controller) GetGrantees(ctx context.Context, ls file.LoadSaver, publish return gl.Get(), nil } -func (c *controller) encryptRefForPublisher(publisherPubKey *ecdsa.PublicKey, ref swarm.Address) (swarm.Address, error) { +func (c *ControllerStruct) encryptRefForPublisher(publisherPubKey *ecdsa.PublicKey, ref swarm.Address) (swarm.Address, error) { keys, err := c.accessLogic.Session.Key(publisherPubKey, [][]byte{oneByteArray}) if err != nil { return swarm.ZeroAddress, err @@ -287,7 +291,7 @@ func (c *controller) encryptRefForPublisher(publisherPubKey *ecdsa.PublicKey, re return swarm.NewAddress(encryptedRef), nil } -func (c *controller) decryptRefForPublisher(publisherPubKey *ecdsa.PublicKey, encryptedRef swarm.Address) (swarm.Address, error) { +func (c *ControllerStruct) decryptRefForPublisher(publisherPubKey *ecdsa.PublicKey, encryptedRef swarm.Address) (swarm.Address, error) { keys, err := c.accessLogic.Session.Key(publisherPubKey, [][]byte{oneByteArray}) if err != nil { return swarm.ZeroAddress, err @@ -308,6 +312,6 @@ func requestPipelineFactory(ctx context.Context, s storage.Putter, encrypt bool, } // TODO: what to do in close ? -func (s *controller) Close() error { +func (s *ControllerStruct) Close() error { return nil } diff --git a/pkg/dynamicaccess/controller_test.go b/pkg/dynamicaccess/controller_test.go index 2b3065899a1..05b4e76db11 100644 --- a/pkg/dynamicaccess/controller_test.go +++ b/pkg/dynamicaccess/controller_test.go @@ -1,3 +1,7 @@ +// Copyright 2024 The Swarm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package dynamicaccess_test import ( @@ -18,6 +22,7 @@ import ( "golang.org/x/crypto/sha3" ) +//nolint:errcheck,gosec,wrapcheck func getHistoryFixture(ctx context.Context, ls file.LoadSaver, al dynamicaccess.ActLogic, publisher *ecdsa.PublicKey) (swarm.Address, error) { h, err := dynamicaccess.NewHistory(ls) if err != nil { @@ -103,11 +108,11 @@ func TestController_PublisherDownload(t *testing.T) { c := dynamicaccess.NewController(al) ls := createLs() ref := swarm.RandAddress(t) - href, err := getHistoryFixture(ctx, ls, al, &publisher.PublicKey) - h, err := dynamicaccess.NewHistoryReference(ls, href) - entry, err := h.Lookup(ctx, time.Now().Unix()) + href, _ := getHistoryFixture(ctx, ls, al, &publisher.PublicKey) + h, _ := dynamicaccess.NewHistoryReference(ls, href) + entry, _ := h.Lookup(ctx, time.Now().Unix()) actRef := entry.Reference() - act, err := kvs.NewReference(ls, actRef) + act, _ := kvs.NewReference(ls, actRef) encRef, err := al.EncryptRef(ctx, act, &publisher.PublicKey, ref) assert.NoError(t, err) @@ -128,12 +133,12 @@ func TestController_GranteeDownload(t *testing.T) { ls := createLs() c := dynamicaccess.NewController(al) ref := swarm.RandAddress(t) - href, err := getHistoryFixture(ctx, ls, publisherAL, &publisher.PublicKey) - h, err := dynamicaccess.NewHistoryReference(ls, href) + href, _ := getHistoryFixture(ctx, ls, publisherAL, &publisher.PublicKey) + h, _ := dynamicaccess.NewHistoryReference(ls, href) ts := time.Date(2001, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() - entry, err := h.Lookup(ctx, ts) + entry, _ := h.Lookup(ctx, ts) actRef := entry.Reference() - act, err := kvs.NewReference(ls, actRef) + act, _ := kvs.NewReference(ls, actRef) encRef, err := publisherAL.EncryptRef(ctx, act, &publisher.PublicKey, ref) assert.NoError(t, err) @@ -178,7 +183,7 @@ func TestController_HandleGrantees(t *testing.T) { assert.Len(t, gl.Get(), 1) addList = []*ecdsa.PublicKey{&getPrivKey(0).PublicKey} - granteeRef, _, _, _, err = c.HandleGrantees(ctx, ls, ls, eglref, href, &publisher.PublicKey, addList, nil) + granteeRef, _, _, _, _ = c.HandleGrantees(ctx, ls, ls, eglref, href, &publisher.PublicKey, addList, nil) gl, err = dynamicaccess.NewGranteeListReference(ls, granteeRef) assert.NoError(t, err) assert.Len(t, gl.Get(), 2) @@ -187,12 +192,12 @@ func TestController_HandleGrantees(t *testing.T) { addList := []*ecdsa.PublicKey{&grantee.PublicKey} revokeList := []*ecdsa.PublicKey{&grantee1.PublicKey} gl, _ := dynamicaccess.NewGranteeList(ls) - gl.Add([]*ecdsa.PublicKey{&publisher.PublicKey, &grantee1.PublicKey}) - granteeRef, err := gl.Save(ctx) + _ = gl.Add([]*ecdsa.PublicKey{&publisher.PublicKey, &grantee1.PublicKey}) + granteeRef, _ := gl.Save(ctx) eglref, _ := refCipher.Encrypt(granteeRef.Bytes()) - granteeRef, _, _, _, err = c.HandleGrantees(ctx, ls, gls, swarm.NewAddress(eglref), href, &publisher.PublicKey, addList, revokeList) - gl, err = dynamicaccess.NewGranteeListReference(ls, granteeRef) + granteeRef, _, _, _, _ = c.HandleGrantees(ctx, ls, gls, swarm.NewAddress(eglref), href, &publisher.PublicKey, addList, revokeList) + gl, err := dynamicaccess.NewGranteeListReference(ls, granteeRef) assert.NoError(t, err) assert.Len(t, gl.Get(), 2) @@ -200,8 +205,9 @@ func TestController_HandleGrantees(t *testing.T) { t.Run("add twice", func(t *testing.T) { addList := []*ecdsa.PublicKey{&grantee.PublicKey, &grantee.PublicKey} + //nolint:ineffassign,staticcheck,wastedassign granteeRef, eglref, _, _, err := c.HandleGrantees(ctx, ls, gls, swarm.ZeroAddress, href, &publisher.PublicKey, addList, nil) - granteeRef, _, _, _, err = c.HandleGrantees(ctx, ls, ls, eglref, href, &publisher.PublicKey, addList, nil) + granteeRef, _, _, _, _ = c.HandleGrantees(ctx, ls, ls, eglref, href, &publisher.PublicKey, addList, nil) gl, err := dynamicaccess.NewGranteeListReference(createLs(), granteeRef) assert.NoError(t, err) @@ -209,7 +215,7 @@ func TestController_HandleGrantees(t *testing.T) { }) t.Run("revoke non-existing", func(t *testing.T) { addList := []*ecdsa.PublicKey{&grantee.PublicKey} - granteeRef, _, _, _, err := c.HandleGrantees(ctx, ls, ls, swarm.ZeroAddress, href, &publisher.PublicKey, addList, nil) + granteeRef, _, _, _, _ := c.HandleGrantees(ctx, ls, ls, swarm.ZeroAddress, href, &publisher.PublicKey, addList, nil) gl, err := dynamicaccess.NewGranteeListReference(createLs(), granteeRef) assert.NoError(t, err) @@ -233,7 +239,7 @@ func TestController_GetGrantees(t *testing.T) { t.Run("get by publisher", func(t *testing.T) { addList := []*ecdsa.PublicKey{&grantee.PublicKey} - granteeRef, eglRef, _, _, err := c1.HandleGrantees(ctx, ls, gls, swarm.ZeroAddress, swarm.ZeroAddress, &publisher.PublicKey, addList, nil) + granteeRef, eglRef, _, _, _ := c1.HandleGrantees(ctx, ls, gls, swarm.ZeroAddress, swarm.ZeroAddress, &publisher.PublicKey, addList, nil) grantees, err := c1.GetGrantees(ctx, ls, &publisher.PublicKey, eglRef) assert.NoError(t, err) @@ -244,7 +250,7 @@ func TestController_GetGrantees(t *testing.T) { }) t.Run("get by non-publisher", func(t *testing.T) { addList := []*ecdsa.PublicKey{&grantee.PublicKey} - _, eglRef, _, _, err := c1.HandleGrantees(ctx, ls, gls, swarm.ZeroAddress, swarm.ZeroAddress, &publisher.PublicKey, addList, nil) + _, eglRef, _, _, _ := c1.HandleGrantees(ctx, ls, gls, swarm.ZeroAddress, swarm.ZeroAddress, &publisher.PublicKey, addList, nil) grantees, err := c2.GetGrantees(ctx, ls, &publisher.PublicKey, eglRef) assert.Error(t, err) assert.Nil(t, grantees) diff --git a/pkg/dynamicaccess/grantee.go b/pkg/dynamicaccess/grantee.go index 02cd8099021..57e6429f7da 100644 --- a/pkg/dynamicaccess/grantee.go +++ b/pkg/dynamicaccess/grantee.go @@ -1,9 +1,14 @@ +// Copyright 2024 The Swarm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package dynamicaccess import ( "context" "crypto/ecdsa" "crypto/elliptic" + "errors" "fmt" "github.com/btcsuite/btcd/btcec/v2" @@ -76,13 +81,18 @@ func (g *GranteeListStruct) Save(ctx context.Context) (swarm.Address, error) { return swarm.NewAddress(refBytes), nil } +var ( + ErrNothingToRemove = errors.New("nothing to remove") + ErrNoGranteeFound = errors.New("no grantee found") +) + func (g *GranteeListStruct) Remove(keysToRemove []*ecdsa.PublicKey) error { if len(keysToRemove) == 0 { - return fmt.Errorf("nothing to remove") + return ErrNothingToRemove } if len(g.grantees) == 0 { - return fmt.Errorf("no grantee found") + return ErrNoGranteeFound } grantees := g.grantees @@ -99,17 +109,17 @@ func (g *GranteeListStruct) Remove(keysToRemove []*ecdsa.PublicKey) error { return nil } -func NewGranteeList(ls file.LoadSaver) (GranteeList, error) { +func NewGranteeList(ls file.LoadSaver) (*GranteeListStruct, error) { // Why is the error necessary? return &GranteeListStruct{ grantees: []*ecdsa.PublicKey{}, loadSave: ls, }, nil } -func NewGranteeListReference(ls file.LoadSaver, reference swarm.Address) (GranteeList, error) { +func NewGranteeListReference(ls file.LoadSaver, reference swarm.Address) (*GranteeListStruct, error) { data, err := ls.Load(context.Background(), reference.Bytes()) if err != nil { - return nil, err + return nil, fmt.Errorf("unable to load reference, %w", err) } grantees := deserialize(data) diff --git a/pkg/dynamicaccess/grantee_test.go b/pkg/dynamicaccess/grantee_test.go index c4ce58ac8aa..cd69aaa752c 100644 --- a/pkg/dynamicaccess/grantee_test.go +++ b/pkg/dynamicaccess/grantee_test.go @@ -1,3 +1,7 @@ +// Copyright 2024 The Swarm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package dynamicaccess_test import ( @@ -33,7 +37,13 @@ func createLs() file.LoadSaver { func generateKeyListFixture() ([]*ecdsa.PublicKey, error) { key1, err := ecdsa.GenerateKey(btcec.S256(), rand.Reader) + if err != nil { + return nil, err + } key2, err := ecdsa.GenerateKey(btcec.S256(), rand.Reader) + if err != nil { + return nil, err + } key3, err := ecdsa.GenerateKey(btcec.S256(), rand.Reader) if err != nil { return nil, err @@ -216,8 +226,8 @@ func TestGranteeRemoveTwo(t *testing.T) { if err != nil { t.Errorf("key generation error: %v", err) } - err = gl.Add([]*ecdsa.PublicKey{keys[0]}) - err = gl.Add([]*ecdsa.PublicKey{keys[0]}) + _ = gl.Add([]*ecdsa.PublicKey{keys[0]}) + _ = gl.Add([]*ecdsa.PublicKey{keys[0]}) err = gl.Remove([]*ecdsa.PublicKey{keys[0]}) assert.NoError(t, err) } diff --git a/pkg/dynamicaccess/history.go b/pkg/dynamicaccess/history.go index 25193adf886..27485e95f95 100644 --- a/pkg/dynamicaccess/history.go +++ b/pkg/dynamicaccess/history.go @@ -1,3 +1,7 @@ +// Copyright 2024 The Swarm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package dynamicaccess import ( @@ -20,44 +24,47 @@ type History interface { Store(ctx context.Context) (swarm.Address, error) } -var _ History = (*history)(nil) +var _ History = (*HistoryStruct)(nil) -var ErrEndIteration = errors.New("end iteration") +var ( + ErrEndIteration = errors.New("end iteration") + ErrUnexpectedType = errors.New("unexpected type") +) -type history struct { +type HistoryStruct struct { manifest *manifest.MantarayManifest ls file.LoadSaver } -func NewHistory(ls file.LoadSaver) (History, error) { +func NewHistory(ls file.LoadSaver) (*HistoryStruct, error) { m, err := manifest.NewDefaultManifest(ls, false) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to create default manifest: %w", err) } mm, ok := m.(*manifest.MantarayManifest) if !ok { - return nil, fmt.Errorf("expected MantarayManifest, got %T", m) + return nil, fmt.Errorf("%w: expected MantarayManifest, got %T", ErrUnexpectedType, m) } - return &history{manifest: mm, ls: ls}, nil + return &HistoryStruct{manifest: mm, ls: ls}, nil } -func NewHistoryReference(ls file.LoadSaver, ref swarm.Address) (History, error) { +func NewHistoryReference(ls file.LoadSaver, ref swarm.Address) (*HistoryStruct, error) { m, err := manifest.NewDefaultManifestReference(ref, ls) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to create default manifest: %w", err) } mm, ok := m.(*manifest.MantarayManifest) if !ok { - return nil, fmt.Errorf("expected MantarayManifest, got %T", m) + return nil, fmt.Errorf("%w: expected MantarayManifest, got %T", ErrUnexpectedType, m) } - return &history{manifest: mm, ls: ls}, nil + return &HistoryStruct{manifest: mm, ls: ls}, nil } -func (h *history) Add(ctx context.Context, ref swarm.Address, timestamp *int64, metadata *map[string]string) error { +func (h *HistoryStruct) Add(ctx context.Context, ref swarm.Address, timestamp *int64, metadata *map[string]string) error { mtdt := map[string]string{} if metadata != nil { mtdt = *metadata @@ -74,10 +81,12 @@ func (h *history) Add(ctx context.Context, ref swarm.Address, timestamp *int64, return h.manifest.Add(ctx, key, manifest.NewEntry(ref, mtdt)) } +var ErrInvalidTimestamp = errors.New("invalid timestamp") + // Lookup finds the entry for a path or returns error if not found -func (h *history) Lookup(ctx context.Context, timestamp int64) (manifest.Entry, error) { +func (h *HistoryStruct) Lookup(ctx context.Context, timestamp int64) (manifest.Entry, error) { if timestamp <= 0 { - return manifest.NewEntry(swarm.ZeroAddress, map[string]string{}), errors.New("invalid timestamp") + return manifest.NewEntry(swarm.ZeroAddress, map[string]string{}), ErrInvalidTimestamp } reversedTimestamp := math.MaxInt64 - timestamp @@ -93,7 +102,7 @@ func (h *history) Lookup(ctx context.Context, timestamp int64) (manifest.Entry, return manifest.NewEntry(swarm.ZeroAddress, map[string]string{}), nil } -func (h *history) lookupNode(ctx context.Context, searchedTimestamp int64) (*mantaray.Node, error) { +func (h *HistoryStruct) lookupNode(ctx context.Context, searchedTimestamp int64) (*mantaray.Node, error) { // before node's timestamp is the closest one that is less than or equal to the searched timestamp // for instance: 2030, 2020, 1994 -> search for 2021 -> before is 2020 var beforeNode *mantaray.Node @@ -134,12 +143,11 @@ func (h *history) lookupNode(ctx context.Context, searchedTimestamp int64) (*man } if afterNode != nil { return afterNode, nil - } return nil, nil } -func (h *history) Store(ctx context.Context) (swarm.Address, error) { +func (h *HistoryStruct) Store(ctx context.Context) (swarm.Address, error) { return h.manifest.Store(ctx) } diff --git a/pkg/dynamicaccess/history_test.go b/pkg/dynamicaccess/history_test.go index 5c7f5670cf6..edc86dc7f22 100644 --- a/pkg/dynamicaccess/history_test.go +++ b/pkg/dynamicaccess/history_test.go @@ -1,3 +1,7 @@ +// Copyright 2024 The Swarm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package dynamicaccess_test import ( @@ -61,27 +65,27 @@ func TestMultiNodeHistoryLookup(t *testing.T) { testActRef1 := swarm.NewAddress([]byte("39a5ea87b141fe44aa609c3327ecd891")) firstTime := time.Date(1994, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() mtdt1 := map[string]string{"firstTime": "1994-04-01"} - h.Add(ctx, testActRef1, &firstTime, &mtdt1) + _ = h.Add(ctx, testActRef1, &firstTime, &mtdt1) testActRef2 := swarm.NewAddress([]byte("39a5ea87b141fe44aa609c3327ecd892")) secondTime := time.Date(2000, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() mtdt2 := map[string]string{"secondTime": "2000-04-01"} - h.Add(ctx, testActRef2, &secondTime, &mtdt2) + _ = h.Add(ctx, testActRef2, &secondTime, &mtdt2) testActRef3 := swarm.NewAddress([]byte("39a5ea87b141fe44aa609c3327ecd893")) thirdTime := time.Date(2015, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() mtdt3 := map[string]string{"thirdTime": "2015-04-01"} - h.Add(ctx, testActRef3, &thirdTime, &mtdt3) + _ = h.Add(ctx, testActRef3, &thirdTime, &mtdt3) testActRef4 := swarm.NewAddress([]byte("39a5ea87b141fe44aa609c3327ecd894")) fourthTime := time.Date(2020, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() mtdt4 := map[string]string{"fourthTime": "2020-04-01"} - h.Add(ctx, testActRef4, &fourthTime, &mtdt4) + _ = h.Add(ctx, testActRef4, &fourthTime, &mtdt4) testActRef5 := swarm.NewAddress([]byte("39a5ea87b141fe44aa609c3327ecd895")) fifthTime := time.Date(2030, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() mtdt5 := map[string]string{"fifthTime": "2030-04-01"} - h.Add(ctx, testActRef5, &fifthTime, &mtdt5) + _ = h.Add(ctx, testActRef5, &fifthTime, &mtdt5) // latest searchedTime := time.Date(1980, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() @@ -126,7 +130,7 @@ func TestHistoryStore(t *testing.T) { testActRef1 := swarm.NewAddress([]byte("39a5ea87b141fe44aa609c3327ecd891")) firstTime := time.Date(1994, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() mtdt1 := map[string]string{"firstTime": "1994-04-01"} - h1.Add(ctx, testActRef1, &firstTime, &mtdt1) + _ = h1.Add(ctx, testActRef1, &firstTime, &mtdt1) href1, err := h1.Store(ctx) assert.NoError(t, err) @@ -134,7 +138,7 @@ func TestHistoryStore(t *testing.T) { h2, err := dynamicaccess.NewHistoryReference(ls, href1) assert.NoError(t, err) - entry1, err := h2.Lookup(ctx, firstTime) + entry1, _ := h2.Lookup(ctx, firstTime) actRef1 := entry1.Reference() assert.True(t, actRef1.Equal(testActRef1)) assert.True(t, reflect.DeepEqual(mtdt1, entry1.Metadata())) diff --git a/pkg/dynamicaccess/mock/accesslogic.go b/pkg/dynamicaccess/mock/accesslogic.go index 8be8e3a07fd..6b48c824eb9 100644 --- a/pkg/dynamicaccess/mock/accesslogic.go +++ b/pkg/dynamicaccess/mock/accesslogic.go @@ -1,3 +1,6 @@ +// Copyright 2024 The Swarm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. package mock type AccessLogicMock struct { diff --git a/pkg/dynamicaccess/mock/controller.go b/pkg/dynamicaccess/mock/controller.go index 09cacb65145..7f77ca2ca32 100644 --- a/pkg/dynamicaccess/mock/controller.go +++ b/pkg/dynamicaccess/mock/controller.go @@ -126,7 +126,7 @@ func (m *mockDacService) UploadHandler(ctx context.Context, ls file.LoadSaver, r } } else { h, _ = dynamicaccess.NewHistory(m.ls) - h.Add(ctx, kvsRef, &now, nil) + _ = h.Add(ctx, kvsRef, &now, nil) historyRef, _ = h.Store(ctx) m.historyMap[historyRef.String()] = h } @@ -140,7 +140,7 @@ func (m *mockDacService) Close() error { return nil } -func (m *mockDacService) HandleGrantees(ctx context.Context, ls file.LoadSaver, gls file.LoadSaver, encryptedglref swarm.Address, historyref swarm.Address, publisher *ecdsa.PublicKey, addList []*ecdsa.PublicKey, removeList []*ecdsa.PublicKey) (swarm.Address, swarm.Address, swarm.Address, swarm.Address, error) { +func (m *mockDacService) HandleGrantees(_ context.Context, ls file.LoadSaver, gls file.LoadSaver, encryptedglref swarm.Address, historyref swarm.Address, publisher *ecdsa.PublicKey, addList []*ecdsa.PublicKey, removeList []*ecdsa.PublicKey) (swarm.Address, swarm.Address, swarm.Address, swarm.Address, error) { historyRef, _ := swarm.ParseHexAddress("67bdf80a9bbea8eca9c8480e43fdceb485d2d74d5708e45144b8c4adacd13d9c") glRef, _ := swarm.ParseHexAddress("3339613565613837623134316665343461613630396333333237656364383934") eglRef, _ := swarm.ParseHexAddress("fc4e9fe978991257b897d987bc4ff13058b66ef45a53189a0b4fe84bb3346396") diff --git a/pkg/dynamicaccess/mock/grantee.go b/pkg/dynamicaccess/mock/grantee.go index 50689ade08f..6a8f01c87cb 100644 --- a/pkg/dynamicaccess/mock/grantee.go +++ b/pkg/dynamicaccess/mock/grantee.go @@ -1,3 +1,7 @@ +// Copyright 2024 The Swarm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package mock import ( diff --git a/pkg/dynamicaccess/mock/session.go b/pkg/dynamicaccess/mock/session.go index 9613aacedfa..6e5d43a9576 100644 --- a/pkg/dynamicaccess/mock/session.go +++ b/pkg/dynamicaccess/mock/session.go @@ -1,3 +1,7 @@ +// Copyright 2024 The Swarm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package mock import ( @@ -17,7 +21,6 @@ func (s *SessionMock) Key(publicKey *ecdsa.PublicKey, nonces [][]byte) ([][]byte return nil, nil } return s.KeyFunc(publicKey, nonces) - } func NewSessionMock(key *ecdsa.PrivateKey) *SessionMock { diff --git a/pkg/dynamicaccess/session.go b/pkg/dynamicaccess/session.go index 0b58c1aa3dd..fb1f44241be 100644 --- a/pkg/dynamicaccess/session.go +++ b/pkg/dynamicaccess/session.go @@ -1,8 +1,13 @@ +// Copyright 2024 The Swarm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package dynamicaccess import ( "crypto/ecdsa" "errors" + "fmt" "github.com/ethersphere/bee/v2/pkg/crypto" "github.com/ethersphere/bee/v2/pkg/keystore" @@ -14,19 +19,24 @@ type Session interface { Key(publicKey *ecdsa.PublicKey, nonces [][]byte) ([][]byte, error) } -var _ Session = (*session)(nil) +var _ Session = (*SessionStruct)(nil) -type session struct { +type SessionStruct struct { key *ecdsa.PrivateKey } -func (s *session) Key(publicKey *ecdsa.PublicKey, nonces [][]byte) ([][]byte, error) { +var ( + ErrInvalidPublicKey = errors.New("invalid public key") + ErrSecretKeyInfinity = errors.New("shared secret is point at infinity") +) + +func (s *SessionStruct) Key(publicKey *ecdsa.PublicKey, nonces [][]byte) ([][]byte, error) { if publicKey == nil { - return nil, errors.New("invalid public key") + return nil, ErrInvalidPublicKey } x, y := publicKey.Curve.ScalarMult(publicKey.X, publicKey.Y, s.key.D.Bytes()) if x == nil || y == nil { - return nil, errors.New("shared secret is point at infinity") + return nil, ErrSecretKeyInfinity } if len(nonces) == 0 { @@ -37,7 +47,7 @@ func (s *session) Key(publicKey *ecdsa.PublicKey, nonces [][]byte) ([][]byte, er for _, nonce := range nonces { key, err := crypto.LegacyKeccak256(append(x.Bytes(), nonce...)) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to get Keccak256 hash: %w", err) } keys = append(keys, key) } @@ -45,13 +55,13 @@ func (s *session) Key(publicKey *ecdsa.PublicKey, nonces [][]byte) ([][]byte, er return keys, nil } -func NewDefaultSession(key *ecdsa.PrivateKey) Session { - return &session{ +func NewDefaultSession(key *ecdsa.PrivateKey) *SessionStruct { + return &SessionStruct{ key: key, } } // Currently implemented only in mock/session.go -func NewFromKeystore(ks keystore.Service, tag, password string) Session { +func NewFromKeystore(keystore.Service, string, string) Session { return nil } diff --git a/pkg/dynamicaccess/session_test.go b/pkg/dynamicaccess/session_test.go index 493b998a805..701384b93bb 100644 --- a/pkg/dynamicaccess/session_test.go +++ b/pkg/dynamicaccess/session_test.go @@ -1,3 +1,7 @@ +// Copyright 2024 The Swarm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package dynamicaccess_test import ( @@ -14,7 +18,7 @@ import ( memkeystore "github.com/ethersphere/bee/v2/pkg/keystore/mem" ) -func mockKeyFunc(publicKey *ecdsa.PublicKey, nonces [][]byte) ([][]byte, error) { +func mockKeyFunc(*ecdsa.PublicKey, [][]byte) ([][]byte, error) { return [][]byte{{1}}, nil } diff --git a/pkg/kvs/kvs_test.go b/pkg/kvs/kvs_test.go index 462e092d532..e2da7aa7030 100644 --- a/pkg/kvs/kvs_test.go +++ b/pkg/kvs/kvs_test.go @@ -33,11 +33,11 @@ func createLs() file.LoadSaver { } func keyValuePair(t *testing.T) ([]byte, []byte) { + t.Helper() return swarm.RandAddress(t).Bytes(), swarm.RandAddress(t).Bytes() } func TestKvs(t *testing.T) { - s, err := kvs.New(createLs()) assert.NoError(t, err) @@ -130,7 +130,7 @@ func TestKvs_Save(t *testing.T) { }) t.Run("Save not empty KVS return valid swarm address", func(t *testing.T) { s, _ := kvs.New(createLs()) - s.Put(ctx, key1, val1) + _ = s.Put(ctx, key1, val1) ref, err := s.Save(ctx) assert.NoError(t, err) assert.True(t, ref.IsValidNonEmpty()) diff --git a/pkg/kvs/mock/kvs.go b/pkg/kvs/mock/kvs.go index 0203cc52ce6..f5818972004 100644 --- a/pkg/kvs/mock/kvs.go +++ b/pkg/kvs/mock/kvs.go @@ -1,3 +1,7 @@ +// Copyright 2024 The Swarm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package mock import ( @@ -24,7 +28,8 @@ func getInMemorySwarm() *single { defer lock.Unlock() if singleInMemorySwarm == nil { singleInMemorySwarm = &single{ - memoryMock: make(map[string]map[string][]byte)} + memoryMock: make(map[string]map[string][]byte), + } } } return singleInMemorySwarm diff --git a/pkg/manifest/mantaray/node.go b/pkg/manifest/mantaray/node.go index 5a59c010d52..9561c299db5 100644 --- a/pkg/manifest/mantaray/node.go +++ b/pkg/manifest/mantaray/node.go @@ -16,13 +16,8 @@ const ( ) var ( - ZeroObfuscationKey []byte -) - -// nolint:gochecknoinits -func init() { ZeroObfuscationKey = make([]byte, 32) -} +) // Error used when lookup path does not match var ( From 4d69d8df06b1358d2f6e88728ead67c135e1bb97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Ar=C3=A1nyi?= Date: Thu, 16 May 2024 16:51:32 +0400 Subject: [PATCH 27/34] refactor(act): typos & docs (#40) refactor(act): typos, docs, removed unused import aliases --- pkg/api/bytes.go | 5 ++--- pkg/api/bzz.go | 9 ++++----- pkg/api/dynamicaccess.go | 18 ++++++++++-------- pkg/api/feed.go | 4 ++-- pkg/dynamicaccess/accesslogic.go | 5 +---- pkg/dynamicaccess/accesslogic_test.go | 14 +++++++------- pkg/dynamicaccess/controller.go | 23 +++++++---------------- 7 files changed, 33 insertions(+), 45 deletions(-) diff --git a/pkg/api/bytes.go b/pkg/api/bytes.go index 2afb1db99c0..0eefb581c1b 100644 --- a/pkg/api/bytes.go +++ b/pkg/api/bytes.go @@ -15,7 +15,7 @@ import ( "github.com/ethersphere/bee/v2/pkg/file/redundancy" "github.com/ethersphere/bee/v2/pkg/jsonhttp" "github.com/ethersphere/bee/v2/pkg/postage" - storage "github.com/ethersphere/bee/v2/pkg/storage" + "github.com/ethersphere/bee/v2/pkg/storage" "github.com/ethersphere/bee/v2/pkg/swarm" "github.com/ethersphere/bee/v2/pkg/tracing" "github.com/gorilla/mux" @@ -124,8 +124,7 @@ func (s *Service) bytesUploadHandler(w http.ResponseWriter, r *http.Request) { return } } - // TODO: what should be the root_address ? (eref vs ref) - span.SetTag("root_address", reference) + span.SetTag("root_address", encryptedReference) err = putter.Done(reference) if err != nil { diff --git a/pkg/api/bzz.go b/pkg/api/bzz.go index b49bc89baea..dc26b26667a 100644 --- a/pkg/api/bzz.go +++ b/pkg/api/bzz.go @@ -30,8 +30,8 @@ import ( "github.com/ethersphere/bee/v2/pkg/log" "github.com/ethersphere/bee/v2/pkg/manifest" "github.com/ethersphere/bee/v2/pkg/postage" - storage "github.com/ethersphere/bee/v2/pkg/storage" - storer "github.com/ethersphere/bee/v2/pkg/storer" + "github.com/ethersphere/bee/v2/pkg/storage" + "github.com/ethersphere/bee/v2/pkg/storer" "github.com/ethersphere/bee/v2/pkg/swarm" "github.com/ethersphere/bee/v2/pkg/topology" "github.com/ethersphere/bee/v2/pkg/tracing" @@ -281,9 +281,8 @@ func (s *Service) fileUploadHandler( ext.LogError(span, err, olog.String("action", "putter.Done")) return } - // TODO: what should be the root_address ? (eref vs ref) span.LogFields(olog.Bool("success", true)) - span.SetTag("root_address", manifestReference) + span.SetTag("root_address", encryptedReference) if tagID != 0 { w.Header().Set(SwarmTagHeader, fmt.Sprint(tagID)) @@ -291,7 +290,7 @@ func (s *Service) fileUploadHandler( } w.Header().Set(ETagHeader, fmt.Sprintf("%q", encryptedReference.String())) w.Header().Set("Access-Control-Expose-Headers", SwarmTagHeader) - // TODO: do we need to return reference as well ? + jsonhttp.Created(w, bzzUploadResponse{ Reference: encryptedReference, }) diff --git a/pkg/api/dynamicaccess.go b/pkg/api/dynamicaccess.go index 32447f905e8..b0be09fbcfa 100644 --- a/pkg/api/dynamicaccess.go +++ b/pkg/api/dynamicaccess.go @@ -21,8 +21,8 @@ import ( "github.com/ethersphere/bee/v2/pkg/file/redundancy" "github.com/ethersphere/bee/v2/pkg/jsonhttp" "github.com/ethersphere/bee/v2/pkg/postage" - storage "github.com/ethersphere/bee/v2/pkg/storage" - storer "github.com/ethersphere/bee/v2/pkg/storer" + "github.com/ethersphere/bee/v2/pkg/storage" + "github.com/ethersphere/bee/v2/pkg/storer" "github.com/ethersphere/bee/v2/pkg/swarm" "github.com/gorilla/mux" ) @@ -40,7 +40,7 @@ func getAddressFromContext(ctx context.Context) swarm.Address { return swarm.ZeroAddress } -// setAddress sets the swarm address in the context +// setAddressInContext sets the swarm address in the context func setAddressInContext(ctx context.Context, address swarm.Address) context.Context { return context.WithValue(ctx, addressKey{}, address) } @@ -121,8 +121,8 @@ func (s *Service) actDecryptionHandler() func(h http.Handler) http.Handler { } } -// actEncryptionHandler is a middleware that encrypts the given address using the publisher's public key -// Uploads the encrypted reference, history and kvs to the store +// actEncryptionHandler is a middleware that encrypts the given address using the publisher's public key, +// uploads the encrypted reference, history and kvs to the store func (s *Service) actEncryptionHandler( ctx context.Context, w http.ResponseWriter, @@ -140,7 +140,7 @@ func (s *Service) actEncryptionHandler( return swarm.ZeroAddress, fmt.Errorf("act failed to encrypt reference: %w", err) } // only need to upload history and kvs if a new history is created, - // meaning that the publsher uploaded to the history for the first time + // meaning that the publisher uploaded to the history for the first time if !historyReference.Equal(historyRootHash) { err = putter.Done(storageReference) if err != nil { @@ -199,7 +199,8 @@ func (s *Service) actListGranteesHandler(w http.ResponseWriter, r *http.Request) jsonhttp.OK(w, granteeSlice) } -// TODO: actGrantRevokeHandler doc. +// actGrantRevokeHandler is a middleware that makes updates to the list of grantees, +// only the publisher is authorized to perform this action func (s *Service) actGrantRevokeHandler(w http.ResponseWriter, r *http.Request) { logger := s.logger.WithName("act_grant_revoke_handler").Build() @@ -363,7 +364,8 @@ func (s *Service) actGrantRevokeHandler(w http.ResponseWriter, r *http.Request) }) } -// TODO: actCreateGranteesHandler doc. +// actCreateGranteesHandler is a middleware that creates a new list of grantees, +// only the publisher is authorized to perform this action func (s *Service) actCreateGranteesHandler(w http.ResponseWriter, r *http.Request) { logger := s.logger.WithName("acthandler").Build() diff --git a/pkg/api/feed.go b/pkg/api/feed.go index 3d43d3d148e..552cd03ffa4 100644 --- a/pkg/api/feed.go +++ b/pkg/api/feed.go @@ -21,8 +21,8 @@ import ( "github.com/ethersphere/bee/v2/pkg/manifest/simple" "github.com/ethersphere/bee/v2/pkg/postage" "github.com/ethersphere/bee/v2/pkg/soc" - storage "github.com/ethersphere/bee/v2/pkg/storage" - storer "github.com/ethersphere/bee/v2/pkg/storer" + "github.com/ethersphere/bee/v2/pkg/storage" + "github.com/ethersphere/bee/v2/pkg/storer" "github.com/ethersphere/bee/v2/pkg/swarm" "github.com/gorilla/mux" ) diff --git a/pkg/dynamicaccess/accesslogic.go b/pkg/dynamicaccess/accesslogic.go index 1cb9db8b5ba..aab18aa1174 100644 --- a/pkg/dynamicaccess/accesslogic.go +++ b/pkg/dynamicaccess/accesslogic.go @@ -9,7 +9,7 @@ import ( "crypto/ecdsa" "fmt" - encryption "github.com/ethersphere/bee/v2/pkg/encryption" + "github.com/ethersphere/bee/v2/pkg/encryption" "github.com/ethersphere/bee/v2/pkg/kvs" "github.com/ethersphere/bee/v2/pkg/swarm" "golang.org/x/crypto/sha3" @@ -26,13 +26,11 @@ var ( type Decryptor interface { // DecryptRef will return a decrypted reference, for given encrypted reference and grantee DecryptRef(ctx context.Context, storage kvs.KeyValueStore, encryptedRef swarm.Address, publisher *ecdsa.PublicKey) (swarm.Address, error) - // Embedding the Session interface Session } // Control interface for the ACT (does write operations). type Control interface { - // Embedding the Decryptor interface Decryptor // AddGrantee adds a new grantee to the ACT AddGrantee(ctx context.Context, storage kvs.KeyValueStore, publisherPubKey, granteePubKey *ecdsa.PublicKey, accessKey *encryption.Key) error @@ -122,7 +120,6 @@ func (al *ActLogic) getAccessKey(ctx context.Context, storage kvs.KeyValueStore, // Generate lookup key and access key decryption key for a given public key func (al *ActLogic) getKeys(publicKey *ecdsa.PublicKey) ([]byte, []byte, error) { nonces := [][]byte{zeroByteArray, oneByteArray} - // keys := make([][]byte, 0, len(nonces)) keys, err := al.Session.Key(publicKey, nonces) if keys == nil { return nil, nil, err diff --git a/pkg/dynamicaccess/accesslogic_test.go b/pkg/dynamicaccess/accesslogic_test.go index 8d6c146e468..a894c90e25f 100644 --- a/pkg/dynamicaccess/accesslogic_test.go +++ b/pkg/dynamicaccess/accesslogic_test.go @@ -77,14 +77,14 @@ func TestDecryptRef_Success(t *testing.T) { t.Error(err) } - acutalRef, err := al.DecryptRef(ctx, s, encryptedRef, &id0.PublicKey) + actualRef, err := al.DecryptRef(ctx, s, encryptedRef, &id0.PublicKey) if err != nil { t.Errorf("There was an error while calling Get: ") t.Error(err) } - if expectedRef.Compare(acutalRef) != 0 { - t.Errorf("Get gave back wrong Swarm reference!") + if expectedRef.Compare(actualRef) != 0 { + t.Errorf("Get returned a wrong Swarm reference!") } } @@ -120,14 +120,14 @@ func TestDecryptRefWithGrantee_Success(t *testing.T) { diffieHellman2 := dynamicaccess.NewDefaultSession(id1) granteeAccessLogic := dynamicaccess.NewLogic(diffieHellman2) - acutalRef, err := granteeAccessLogic.DecryptRef(ctx, s, encryptedRef, &id0.PublicKey) + actualRef, err := granteeAccessLogic.DecryptRef(ctx, s, encryptedRef, &id0.PublicKey) if err != nil { t.Errorf("There was an error while calling Get: ") t.Error(err) } - if expectedRef.Compare(acutalRef) != 0 { - t.Errorf("Get gave back wrong Swarm reference!") + if expectedRef.Compare(actualRef) != 0 { + t.Errorf("Get returned a wrong Swarm reference!") } } @@ -169,7 +169,7 @@ func TestAddPublisher(t *testing.T) { decodedEncryptedAccessKey := hex.EncodeToString(encryptedAccessKey) - // A random value is returned so it is only possibly to check the length of the returned value + // A random value is returned, so it is only possible to check the length of the returned value // We know the lookup key because the generated private key is fixed if len(decodedEncryptedAccessKey) != 64 { t.Errorf("AddPublisher: expected encrypted access key length 64, got %d", len(decodedEncryptedAccessKey)) diff --git a/pkg/dynamicaccess/controller.go b/pkg/dynamicaccess/controller.go index a8998aa12df..911c59f3f27 100644 --- a/pkg/dynamicaccess/controller.go +++ b/pkg/dynamicaccess/controller.go @@ -10,18 +10,15 @@ import ( "io" "time" - encryption "github.com/ethersphere/bee/v2/pkg/encryption" + "github.com/ethersphere/bee/v2/pkg/encryption" "github.com/ethersphere/bee/v2/pkg/file" - "github.com/ethersphere/bee/v2/pkg/file/pipeline" - "github.com/ethersphere/bee/v2/pkg/file/pipeline/builder" - "github.com/ethersphere/bee/v2/pkg/file/redundancy" "github.com/ethersphere/bee/v2/pkg/kvs" - "github.com/ethersphere/bee/v2/pkg/storage" "github.com/ethersphere/bee/v2/pkg/swarm" ) type GranteeManager interface { - // TODO: doc + // HandleGrantees manages the grantees for the given publisher, updating the list based on provided public keys to add or remove. + // Only the publisher can make changes to the grantee list. HandleGrantees(ctx context.Context, ls file.LoadSaver, gls file.LoadSaver, granteeref swarm.Address, historyref swarm.Address, publisher *ecdsa.PublicKey, addList, removeList []*ecdsa.PublicKey) (swarm.Address, swarm.Address, swarm.Address, swarm.Address, error) // GetGrantees returns the list of grantees for the given publisher. // The list is accessible only by the publisher. @@ -70,7 +67,7 @@ func (c *ControllerStruct) DownloadHandler( func (c *ControllerStruct) UploadHandler( ctx context.Context, ls file.LoadSaver, - refrefence swarm.Address, + reference swarm.Address, publisher *ecdsa.PublicKey, historyRootHash swarm.Address, ) (swarm.Address, swarm.Address, swarm.Address, error) { @@ -121,7 +118,7 @@ func (c *ControllerStruct) UploadHandler( } } - encryptedRef, err := c.accessLogic.EncryptRef(ctx, storage, publisher, refrefence) + encryptedRef, err := c.accessLogic.EncryptRef(ctx, storage, publisher, reference) return actRef, historyRef, encryptedRef, err } @@ -265,7 +262,7 @@ func (c *ControllerStruct) HandleGrantees( return glref, eglref, href, actref, nil } -func (c *ControllerStruct) GetGrantees(ctx context.Context, ls file.LoadSaver, publisher *ecdsa.PublicKey, encryptedglref swarm.Address) ([]*ecdsa.PublicKey, error) { +func (c *ControllerStruct) GetGrantees(_ context.Context, ls file.LoadSaver, publisher *ecdsa.PublicKey, encryptedglref swarm.Address) ([]*ecdsa.PublicKey, error) { granteeRef, err := c.decryptRefForPublisher(publisher, encryptedglref) if err != nil { return nil, err @@ -305,13 +302,7 @@ func (c *ControllerStruct) decryptRefForPublisher(publisherPubKey *ecdsa.PublicK return swarm.NewAddress(ref), nil } -func requestPipelineFactory(ctx context.Context, s storage.Putter, encrypt bool, rLevel redundancy.Level) func() pipeline.Interface { - return func() pipeline.Interface { - return builder.NewPipelineBuilder(ctx, s, encrypt, rLevel) - } -} - // TODO: what to do in close ? -func (s *ControllerStruct) Close() error { +func (c *ControllerStruct) Close() error { return nil } From afccdbd40d767b3297bdc71a5457bdbea0a26545 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A1lint=20Ujv=C3=A1ri?= <58116288+bosi95@users.noreply.github.com> Date: Thu, 16 May 2024 15:46:43 +0200 Subject: [PATCH 28/34] refactor(act): naming and fix remaining PR comments (#42) * Refactor naming and fix remaining PR comments * use ctx in grantees.get * remove act_ucs.md --- act_ucs.md | 86 --------------------------- pkg/api/dynamicaccess.go | 6 +- pkg/dynamicaccess/accesslogic_test.go | 34 +++++------ pkg/dynamicaccess/controller.go | 20 +++---- pkg/dynamicaccess/controller_test.go | 36 +++++------ pkg/dynamicaccess/grantee.go | 4 +- pkg/dynamicaccess/grantee_test.go | 6 +- pkg/dynamicaccess/mock/controller.go | 4 +- 8 files changed, 53 insertions(+), 143 deletions(-) delete mode 100644 act_ucs.md diff --git a/act_ucs.md b/act_ucs.md deleted file mode 100644 index 14f93f346e4..00000000000 --- a/act_ucs.md +++ /dev/null @@ -1,86 +0,0 @@ -# ACT user stories - -This file contains the SWARM ACT user stories. - -ZenHub Link: [SpDevTeam](https://app.zenhub.com/workspaces/spdevteam-6544d91246b817002d853e69/board) - -### Context & definitions -- Content is created as a 'channel', i.e. as a feed with a specific topic. -- The content is uploaded to the feed, and new version of the content could be uploaded any time. -- The content can be accessed by the publisher and any viewers if it does not have ACT defined. -- The content can be accessed by the publisher and any viewers if it has ACT defined and the viewer is in the grantees list. -- A publisher is a user who is uploading new content. -- A viewer is a user who is accessing content. -- When a viewer is losing access it means that in the newly generated ACT the viewer will no longer be present in the grantee list. -- The publisher should be able to modify the ACT, using the same private key, but on multiple nodes in parallel. -- The publisher should be able to modify the grantee list, using the same private key, but on multiple nodes in parallel (this would potentially require the grantee list to be encrypted as well). - -### Use cases - -- [ ] **1** -- I'm a publisher -- I upload some new content -- I create an ACT for the content, but without additional grantees -- I can access the content -___ - -- [ ] **1/a** -- I'm a publisher -- I have an existing ACT for some content -- I can read and edit the ACT -___ - -- [ ] **2** -- I'm a publisher -- I have an existing ACT for some content, but without additional grantees -- I grant access to the content for some new viewers -- Those viewers can access the content -___ - -- [ ] **2/a** -- I'm a publisher -- I have an existing ACT for some content with some existing grantees -- I grant access to the content to additional viewers -- The existing viewers can access the content still -- The additional viewers can access the content -___ - -- [ ] **2/b** -- I'm a publisher -- I remove viewers from the grantee list of the ACT content -- The content is unchanged -- The removed viewers can access the content still -- The existing viewers can access the content still -___ - -- [ ] **2/c** -- I'm a publisher -- I remove viewers from the grantee list of the ACT content -- The content is updated -- The removed viewers can't access the latest version of content anymore -- The removed viewers can access an older version of the content, the version up until the moment they were removed -- The existing viewers can access the content still, including the latest version -___ - -- [ ] **3** -- I'm a viewer -- I requested access to the content -- If I was granted access, I can access the content -- If I was not granted access, I can't access the content still -___ - -- [ ] **4** -- I'm a viewer -- I had access to the content until now -- My access to the content got revoked -- The content is unchanged -- I can access the content still -___ - -- [ ] **4/a** -- I'm a viewer -- I had access to the content until now -- My access to the content got revoked -- The content is updated -- I can't access versions of the content that were uploaded after I lost access -___ diff --git a/pkg/api/dynamicaccess.go b/pkg/api/dynamicaccess.go index b0be09fbcfa..e776857d190 100644 --- a/pkg/api/dynamicaccess.go +++ b/pkg/api/dynamicaccess.go @@ -185,7 +185,7 @@ func (s *Service) actListGranteesHandler(w http.ResponseWriter, r *http.Request) } publisher := &s.publicKey ls := loadsave.NewReadonly(s.storer.Download(cache)) - grantees, err := s.dac.GetGrantees(r.Context(), ls, publisher, paths.GranteesAddress) + grantees, err := s.dac.Get(r.Context(), ls, publisher, paths.GranteesAddress) if err != nil { logger.Debug("could not get grantees", "error", err) logger.Error(nil, "could not get grantees") @@ -326,7 +326,7 @@ func (s *Service) actGrantRevokeHandler(w http.ResponseWriter, r *http.Request) publisher := &s.publicKey ls := loadsave.New(s.storer.ChunkStore(), s.storer.Cache(), requestPipelineFactory(ctx, putter, false, redundancy.NONE)) gls := loadsave.New(s.storer.ChunkStore(), s.storer.Cache(), requestPipelineFactory(ctx, putter, granteeListEncrypt, redundancy.NONE)) - granteeref, encryptedglref, historyref, actref, err := s.dac.HandleGrantees(ctx, ls, gls, granteeref, historyAddress, publisher, grantees.Addlist, grantees.Revokelist) + granteeref, encryptedglref, historyref, actref, err := s.dac.UpdateHandler(ctx, ls, gls, granteeref, historyAddress, publisher, grantees.Addlist, grantees.Revokelist) if err != nil { logger.Debug("failed to update grantee list", "error", err) logger.Error(nil, "failed to update grantee list") @@ -471,7 +471,7 @@ func (s *Service) actCreateGranteesHandler(w http.ResponseWriter, r *http.Reques publisher := &s.publicKey ls := loadsave.New(s.storer.ChunkStore(), s.storer.Cache(), requestPipelineFactory(ctx, putter, false, redundancy.NONE)) gls := loadsave.New(s.storer.ChunkStore(), s.storer.Cache(), requestPipelineFactory(ctx, putter, granteeListEncrypt, redundancy.NONE)) - granteeref, encryptedglref, historyref, actref, err := s.dac.HandleGrantees(ctx, ls, gls, swarm.ZeroAddress, historyAddress, publisher, list, nil) + granteeref, encryptedglref, historyref, actref, err := s.dac.UpdateHandler(ctx, ls, gls, swarm.ZeroAddress, historyAddress, publisher, list, nil) if err != nil { logger.Debug("failed to update grantee list", "error", err) logger.Error(nil, "failed to update grantee list") diff --git a/pkg/dynamicaccess/accesslogic_test.go b/pkg/dynamicaccess/accesslogic_test.go index a894c90e25f..93b65f1bdaf 100644 --- a/pkg/dynamicaccess/accesslogic_test.go +++ b/pkg/dynamicaccess/accesslogic_test.go @@ -62,7 +62,7 @@ func TestDecryptRef_Success(t *testing.T) { al := setupAccessLogic() err := al.AddPublisher(ctx, s, &id0.PublicKey) if err != nil { - t.Errorf("AddPublisher: expected no error, got %v", err) + t.Fatalf("AddPublisher: expected no error, got %v", err) } byteRef, _ := hex.DecodeString("39a5ea87b141fe44aa609c3327ecd896c0e2122897f5f4bbacf74db1033c5559") @@ -73,18 +73,16 @@ func TestDecryptRef_Success(t *testing.T) { encryptedRef, err := al.EncryptRef(ctx, s, &id0.PublicKey, expectedRef) t.Logf("encryptedRef: %s", encryptedRef.String()) if err != nil { - t.Errorf("There was an error while calling EncryptRef: ") - t.Error(err) + t.Fatalf("There was an error while calling EncryptRef: %v", err) } actualRef, err := al.DecryptRef(ctx, s, encryptedRef, &id0.PublicKey) if err != nil { - t.Errorf("There was an error while calling Get: ") - t.Error(err) + t.Fatalf("There was an error while calling Get: %v", err) } if expectedRef.Compare(actualRef) != 0 { - t.Errorf("Get returned a wrong Swarm reference!") + t.Fatalf("Get gave back wrong Swarm reference!") } } @@ -97,13 +95,13 @@ func TestDecryptRefWithGrantee_Success(t *testing.T) { s := kvsmock.New() err := al.AddPublisher(ctx, s, &id0.PublicKey) if err != nil { - t.Errorf("AddPublisher: expected no error, got %v", err) + t.Fatalf("AddPublisher: expected no error, got %v", err) } id1, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) err = al.AddGrantee(ctx, s, &id0.PublicKey, &id1.PublicKey, nil) if err != nil { - t.Errorf("AddNewGrantee: expected no error, got %v", err) + t.Fatalf("AddNewGrantee: expected no error, got %v", err) } byteRef, _ := hex.DecodeString("39a5ea87b141fe44aa609c3327ecd896c0e2122897f5f4bbacf74db1033c5559") @@ -114,20 +112,18 @@ func TestDecryptRefWithGrantee_Success(t *testing.T) { encryptedRef, err := al.EncryptRef(ctx, s, &id0.PublicKey, expectedRef) t.Logf("encryptedRef: %s", encryptedRef.String()) if err != nil { - t.Errorf("There was an error while calling EncryptRef: ") - t.Error(err) + t.Fatalf("There was an error while calling EncryptRef: %v", err) } diffieHellman2 := dynamicaccess.NewDefaultSession(id1) granteeAccessLogic := dynamicaccess.NewLogic(diffieHellman2) actualRef, err := granteeAccessLogic.DecryptRef(ctx, s, encryptedRef, &id0.PublicKey) if err != nil { - t.Errorf("There was an error while calling Get: ") - t.Error(err) + t.Fatalf("There was an error while calling Get: %v", err) } if expectedRef.Compare(actualRef) != 0 { - t.Errorf("Get returned a wrong Swarm reference!") + t.Fatalf("Get gave back wrong Swarm reference!") } } @@ -147,7 +143,7 @@ func TestDecryptRef_Error(t *testing.T) { r, err := al.DecryptRef(ctx, s, encryptedRef, nil) if err == nil { t.Logf("r: %s", r.String()) - t.Errorf("Get should return encrypted access key not found error!") + t.Fatalf("Get should return encrypted access key not found error!") } } @@ -172,10 +168,10 @@ func TestAddPublisher(t *testing.T) { // A random value is returned, so it is only possible to check the length of the returned value // We know the lookup key because the generated private key is fixed if len(decodedEncryptedAccessKey) != 64 { - t.Errorf("AddPublisher: expected encrypted access key length 64, got %d", len(decodedEncryptedAccessKey)) + t.Fatalf("AddPublisher: expected encrypted access key length 64, got %d", len(decodedEncryptedAccessKey)) } if s == nil { - t.Errorf("AddPublisher: expected act, got nil") + t.Fatalf("AddPublisher: expected act, got nil") } } @@ -206,7 +202,7 @@ func TestAddNewGranteeToContent(t *testing.T) { result, _ := s.Get(ctx, lookupKeyAsByte) hexEncodedEncryptedAK := hex.EncodeToString(result) if len(hexEncodedEncryptedAK) != 64 { - t.Errorf("AddNewGrantee: expected encrypted access key length 64, got %d", len(hexEncodedEncryptedAK)) + t.Fatalf("AddNewGrantee: expected encrypted access key length 64, got %d", len(hexEncodedEncryptedAK)) } lookupKeyAsByte, err = hex.DecodeString(firstAddedGranteeLookupKey) @@ -215,7 +211,7 @@ func TestAddNewGranteeToContent(t *testing.T) { result, _ = s.Get(ctx, lookupKeyAsByte) hexEncodedEncryptedAK = hex.EncodeToString(result) if len(hexEncodedEncryptedAK) != 64 { - t.Errorf("AddNewGrantee: expected encrypted access key length 64, got %d", len(hexEncodedEncryptedAK)) + t.Fatalf("AddNewGrantee: expected encrypted access key length 64, got %d", len(hexEncodedEncryptedAK)) } lookupKeyAsByte, err = hex.DecodeString(secondAddedGranteeLookupKey) @@ -224,6 +220,6 @@ func TestAddNewGranteeToContent(t *testing.T) { result, _ = s.Get(ctx, lookupKeyAsByte) hexEncodedEncryptedAK = hex.EncodeToString(result) if len(hexEncodedEncryptedAK) != 64 { - t.Errorf("AddNewGrantee: expected encrypted access key length 64, got %d", len(hexEncodedEncryptedAK)) + t.Fatalf("AddNewGrantee: expected encrypted access key length 64, got %d", len(hexEncodedEncryptedAK)) } } diff --git a/pkg/dynamicaccess/controller.go b/pkg/dynamicaccess/controller.go index 911c59f3f27..2b3c9e24ace 100644 --- a/pkg/dynamicaccess/controller.go +++ b/pkg/dynamicaccess/controller.go @@ -16,17 +16,17 @@ import ( "github.com/ethersphere/bee/v2/pkg/swarm" ) -type GranteeManager interface { - // HandleGrantees manages the grantees for the given publisher, updating the list based on provided public keys to add or remove. +type Grantees interface { + // UpdateHandler manages the grantees for the given publisher, updating the list based on provided public keys to add or remove. // Only the publisher can make changes to the grantee list. - HandleGrantees(ctx context.Context, ls file.LoadSaver, gls file.LoadSaver, granteeref swarm.Address, historyref swarm.Address, publisher *ecdsa.PublicKey, addList, removeList []*ecdsa.PublicKey) (swarm.Address, swarm.Address, swarm.Address, swarm.Address, error) - // GetGrantees returns the list of grantees for the given publisher. + UpdateHandler(ctx context.Context, ls file.LoadSaver, gls file.LoadSaver, granteeref swarm.Address, historyref swarm.Address, publisher *ecdsa.PublicKey, addList, removeList []*ecdsa.PublicKey) (swarm.Address, swarm.Address, swarm.Address, swarm.Address, error) + // Get returns the list of grantees for the given publisher. // The list is accessible only by the publisher. - GetGrantees(ctx context.Context, ls file.LoadSaver, publisher *ecdsa.PublicKey, encryptedglref swarm.Address) ([]*ecdsa.PublicKey, error) + Get(ctx context.Context, ls file.LoadSaver, publisher *ecdsa.PublicKey, encryptedglref swarm.Address) ([]*ecdsa.PublicKey, error) } type Controller interface { - GranteeManager + Grantees // DownloadHandler decrypts the encryptedRef using the lookupkey based on the history and timestamp. DownloadHandler(ctx context.Context, ls file.LoadSaver, encryptedRef swarm.Address, publisher *ecdsa.PublicKey, historyRootHash swarm.Address, timestamp int64) (swarm.Address, error) // UploadHandler encrypts the reference and stores it in the history as the latest update. @@ -128,7 +128,7 @@ func NewController(accessLogic ActLogic) *ControllerStruct { } } -func (c *ControllerStruct) HandleGrantees( +func (c *ControllerStruct) UpdateHandler( ctx context.Context, ls file.LoadSaver, gls file.LoadSaver, @@ -186,7 +186,7 @@ func (c *ControllerStruct) HandleGrantees( return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err } - gl, err = NewGranteeListReference(gls, granteeref) + gl, err = NewGranteeListReference(ctx, gls, granteeref) if err != nil { return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err } @@ -262,12 +262,12 @@ func (c *ControllerStruct) HandleGrantees( return glref, eglref, href, actref, nil } -func (c *ControllerStruct) GetGrantees(_ context.Context, ls file.LoadSaver, publisher *ecdsa.PublicKey, encryptedglref swarm.Address) ([]*ecdsa.PublicKey, error) { +func (c *ControllerStruct) Get(ctx context.Context, ls file.LoadSaver, publisher *ecdsa.PublicKey, encryptedglref swarm.Address) ([]*ecdsa.PublicKey, error) { granteeRef, err := c.decryptRefForPublisher(publisher, encryptedglref) if err != nil { return nil, err } - gl, err := NewGranteeListReference(ls, granteeRef) + gl, err := NewGranteeListReference(ctx, ls, granteeRef) if err != nil { return nil, err } diff --git a/pkg/dynamicaccess/controller_test.go b/pkg/dynamicaccess/controller_test.go index 05b4e76db11..6aa7757a5ca 100644 --- a/pkg/dynamicaccess/controller_test.go +++ b/pkg/dynamicaccess/controller_test.go @@ -164,27 +164,27 @@ func TestController_HandleGrantees(t *testing.T) { t.Run("add to new list", func(t *testing.T) { addList := []*ecdsa.PublicKey{&grantee.PublicKey} - granteeRef, _, _, _, err := c.HandleGrantees(ctx, ls, ls, swarm.ZeroAddress, swarm.ZeroAddress, &publisher.PublicKey, addList, nil) + granteeRef, _, _, _, err := c.UpdateHandler(ctx, ls, ls, swarm.ZeroAddress, swarm.ZeroAddress, &publisher.PublicKey, addList, nil) assert.NoError(t, err) - gl, err := dynamicaccess.NewGranteeListReference(ls, granteeRef) + gl, err := dynamicaccess.NewGranteeListReference(ctx, ls, granteeRef) assert.NoError(t, err) assert.Len(t, gl.Get(), 1) }) t.Run("add to existing list", func(t *testing.T) { addList := []*ecdsa.PublicKey{&grantee.PublicKey} - granteeRef, eglref, _, _, err := c.HandleGrantees(ctx, ls, gls, swarm.ZeroAddress, href, &publisher.PublicKey, addList, nil) + granteeRef, eglref, _, _, err := c.UpdateHandler(ctx, ls, gls, swarm.ZeroAddress, href, &publisher.PublicKey, addList, nil) assert.NoError(t, err) - gl, err := dynamicaccess.NewGranteeListReference(ls, granteeRef) + gl, err := dynamicaccess.NewGranteeListReference(ctx, ls, granteeRef) assert.NoError(t, err) assert.Len(t, gl.Get(), 1) addList = []*ecdsa.PublicKey{&getPrivKey(0).PublicKey} - granteeRef, _, _, _, _ = c.HandleGrantees(ctx, ls, ls, eglref, href, &publisher.PublicKey, addList, nil) - gl, err = dynamicaccess.NewGranteeListReference(ls, granteeRef) + granteeRef, _, _, _, _ = c.UpdateHandler(ctx, ls, ls, eglref, href, &publisher.PublicKey, addList, nil) + gl, err = dynamicaccess.NewGranteeListReference(ctx, ls, granteeRef) assert.NoError(t, err) assert.Len(t, gl.Get(), 2) }) @@ -196,8 +196,8 @@ func TestController_HandleGrantees(t *testing.T) { granteeRef, _ := gl.Save(ctx) eglref, _ := refCipher.Encrypt(granteeRef.Bytes()) - granteeRef, _, _, _, _ = c.HandleGrantees(ctx, ls, gls, swarm.NewAddress(eglref), href, &publisher.PublicKey, addList, revokeList) - gl, err := dynamicaccess.NewGranteeListReference(ls, granteeRef) + granteeRef, _, _, _, _ = c.UpdateHandler(ctx, ls, gls, swarm.NewAddress(eglref), href, &publisher.PublicKey, addList, revokeList) + gl, err := dynamicaccess.NewGranteeListReference(ctx, ls, granteeRef) assert.NoError(t, err) assert.Len(t, gl.Get(), 2) @@ -206,17 +206,17 @@ func TestController_HandleGrantees(t *testing.T) { t.Run("add twice", func(t *testing.T) { addList := []*ecdsa.PublicKey{&grantee.PublicKey, &grantee.PublicKey} //nolint:ineffassign,staticcheck,wastedassign - granteeRef, eglref, _, _, err := c.HandleGrantees(ctx, ls, gls, swarm.ZeroAddress, href, &publisher.PublicKey, addList, nil) - granteeRef, _, _, _, _ = c.HandleGrantees(ctx, ls, ls, eglref, href, &publisher.PublicKey, addList, nil) - gl, err := dynamicaccess.NewGranteeListReference(createLs(), granteeRef) + granteeRef, eglref, _, _, err := c.UpdateHandler(ctx, ls, gls, swarm.ZeroAddress, href, &publisher.PublicKey, addList, nil) + granteeRef, _, _, _, _ = c.UpdateHandler(ctx, ls, ls, eglref, href, &publisher.PublicKey, addList, nil) + gl, err := dynamicaccess.NewGranteeListReference(ctx, createLs(), granteeRef) assert.NoError(t, err) assert.Len(t, gl.Get(), 1) }) t.Run("revoke non-existing", func(t *testing.T) { addList := []*ecdsa.PublicKey{&grantee.PublicKey} - granteeRef, _, _, _, _ := c.HandleGrantees(ctx, ls, ls, swarm.ZeroAddress, href, &publisher.PublicKey, addList, nil) - gl, err := dynamicaccess.NewGranteeListReference(createLs(), granteeRef) + granteeRef, _, _, _, _ := c.UpdateHandler(ctx, ls, ls, swarm.ZeroAddress, href, &publisher.PublicKey, addList, nil) + gl, err := dynamicaccess.NewGranteeListReference(ctx, createLs(), granteeRef) assert.NoError(t, err) assert.Len(t, gl.Get(), 1) @@ -239,19 +239,19 @@ func TestController_GetGrantees(t *testing.T) { t.Run("get by publisher", func(t *testing.T) { addList := []*ecdsa.PublicKey{&grantee.PublicKey} - granteeRef, eglRef, _, _, _ := c1.HandleGrantees(ctx, ls, gls, swarm.ZeroAddress, swarm.ZeroAddress, &publisher.PublicKey, addList, nil) + granteeRef, eglRef, _, _, _ := c1.UpdateHandler(ctx, ls, gls, swarm.ZeroAddress, swarm.ZeroAddress, &publisher.PublicKey, addList, nil) - grantees, err := c1.GetGrantees(ctx, ls, &publisher.PublicKey, eglRef) + grantees, err := c1.Get(ctx, ls, &publisher.PublicKey, eglRef) assert.NoError(t, err) assert.True(t, reflect.DeepEqual(grantees, addList)) - gl, _ := dynamicaccess.NewGranteeListReference(ls, granteeRef) + gl, _ := dynamicaccess.NewGranteeListReference(ctx, ls, granteeRef) assert.True(t, reflect.DeepEqual(gl.Get(), addList)) }) t.Run("get by non-publisher", func(t *testing.T) { addList := []*ecdsa.PublicKey{&grantee.PublicKey} - _, eglRef, _, _, _ := c1.HandleGrantees(ctx, ls, gls, swarm.ZeroAddress, swarm.ZeroAddress, &publisher.PublicKey, addList, nil) - grantees, err := c2.GetGrantees(ctx, ls, &publisher.PublicKey, eglRef) + _, eglRef, _, _, _ := c1.UpdateHandler(ctx, ls, gls, swarm.ZeroAddress, swarm.ZeroAddress, &publisher.PublicKey, addList, nil) + grantees, err := c2.Get(ctx, ls, &publisher.PublicKey, eglRef) assert.Error(t, err) assert.Nil(t, grantees) }) diff --git a/pkg/dynamicaccess/grantee.go b/pkg/dynamicaccess/grantee.go index 57e6429f7da..44832032754 100644 --- a/pkg/dynamicaccess/grantee.go +++ b/pkg/dynamicaccess/grantee.go @@ -116,8 +116,8 @@ func NewGranteeList(ls file.LoadSaver) (*GranteeListStruct, error) { // Why is t }, nil } -func NewGranteeListReference(ls file.LoadSaver, reference swarm.Address) (*GranteeListStruct, error) { - data, err := ls.Load(context.Background(), reference.Bytes()) +func NewGranteeListReference(ctx context.Context, ls file.LoadSaver, reference swarm.Address) (*GranteeListStruct, error) { + data, err := ls.Load(ctx, reference.Bytes()) if err != nil { return nil, fmt.Errorf("unable to load reference, %w", err) } diff --git a/pkg/dynamicaccess/grantee_test.go b/pkg/dynamicaccess/grantee_test.go index cd69aaa752c..91e44569b2a 100644 --- a/pkg/dynamicaccess/grantee_test.go +++ b/pkg/dynamicaccess/grantee_test.go @@ -169,7 +169,7 @@ func TestGranteeSave(t *testing.T) { t.Errorf("key generation error: %v", err) } t.Run("Create grantee list with invalid reference, expect error", func(t *testing.T) { - gl, err := dynamicaccess.NewGranteeListReference(createLs(), swarm.RandAddress(t)) + gl, err := dynamicaccess.NewGranteeListReference(ctx, createLs(), swarm.RandAddress(t)) assert.Error(t, err) assert.Nil(t, gl) }) @@ -195,7 +195,7 @@ func TestGranteeSave(t *testing.T) { ref, err := gl1.Save(ctx) assert.NoError(t, err) - gl2, _ := dynamicaccess.NewGranteeListReference(ls, ref) + gl2, _ := dynamicaccess.NewGranteeListReference(ctx, ls, ref) val := gl2.Get() assert.NoError(t, err) assert.Equal(t, keys, val) @@ -211,7 +211,7 @@ func TestGranteeSave(t *testing.T) { ref, err := gl1.Save(ctx) assert.NoError(t, err) - gl2, _ := dynamicaccess.NewGranteeListReference(ls, ref) + gl2, _ := dynamicaccess.NewGranteeListReference(ctx, ls, ref) err = gl2.Add(keys2) assert.NoError(t, err) diff --git a/pkg/dynamicaccess/mock/controller.go b/pkg/dynamicaccess/mock/controller.go index 7f77ca2ca32..3db86c88020 100644 --- a/pkg/dynamicaccess/mock/controller.go +++ b/pkg/dynamicaccess/mock/controller.go @@ -140,7 +140,7 @@ func (m *mockDacService) Close() error { return nil } -func (m *mockDacService) HandleGrantees(_ context.Context, ls file.LoadSaver, gls file.LoadSaver, encryptedglref swarm.Address, historyref swarm.Address, publisher *ecdsa.PublicKey, addList []*ecdsa.PublicKey, removeList []*ecdsa.PublicKey) (swarm.Address, swarm.Address, swarm.Address, swarm.Address, error) { +func (m *mockDacService) UpdateHandler(_ context.Context, ls file.LoadSaver, gls file.LoadSaver, encryptedglref swarm.Address, historyref swarm.Address, publisher *ecdsa.PublicKey, addList []*ecdsa.PublicKey, removeList []*ecdsa.PublicKey) (swarm.Address, swarm.Address, swarm.Address, swarm.Address, error) { historyRef, _ := swarm.ParseHexAddress("67bdf80a9bbea8eca9c8480e43fdceb485d2d74d5708e45144b8c4adacd13d9c") glRef, _ := swarm.ParseHexAddress("3339613565613837623134316665343461613630396333333237656364383934") eglRef, _ := swarm.ParseHexAddress("fc4e9fe978991257b897d987bc4ff13058b66ef45a53189a0b4fe84bb3346396") @@ -148,7 +148,7 @@ func (m *mockDacService) HandleGrantees(_ context.Context, ls file.LoadSaver, gl return glRef, eglRef, historyRef, actref, nil } -func (m *mockDacService) GetGrantees(ctx context.Context, ls file.LoadSaver, publisher *ecdsa.PublicKey, encryptedglref swarm.Address) ([]*ecdsa.PublicKey, error) { +func (m *mockDacService) Get(ctx context.Context, ls file.LoadSaver, publisher *ecdsa.PublicKey, encryptedglref swarm.Address) ([]*ecdsa.PublicKey, error) { if m.publisher == "" { return nil, fmt.Errorf("granteelist not found") } From c0fe5975f18b4e1cb0e37f03b3204c6977af8d0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A1lint=20Ujv=C3=A1ri?= <58116288+bosi95@users.noreply.github.com> Date: Fri, 17 May 2024 09:52:51 +0200 Subject: [PATCH 29/34] Refactor accesslogic.AddGrantee and parallelize tests (#43) --- pkg/dynamicaccess/accesslogic.go | 19 ++++------- pkg/dynamicaccess/accesslogic_test.go | 49 +++++++++++++-------------- pkg/dynamicaccess/controller.go | 8 ++--- pkg/dynamicaccess/controller_test.go | 19 +++++++---- pkg/dynamicaccess/grantee_test.go | 3 ++ pkg/dynamicaccess/history_test.go | 4 +++ pkg/kvs/kvs_test.go | 2 ++ 7 files changed, 55 insertions(+), 49 deletions(-) diff --git a/pkg/dynamicaccess/accesslogic.go b/pkg/dynamicaccess/accesslogic.go index aab18aa1174..671537486e9 100644 --- a/pkg/dynamicaccess/accesslogic.go +++ b/pkg/dynamicaccess/accesslogic.go @@ -33,7 +33,7 @@ type Decryptor interface { type Control interface { Decryptor // AddGrantee adds a new grantee to the ACT - AddGrantee(ctx context.Context, storage kvs.KeyValueStore, publisherPubKey, granteePubKey *ecdsa.PublicKey, accessKey *encryption.Key) error + AddGrantee(ctx context.Context, storage kvs.KeyValueStore, publisherPubKey, granteePubKey *ecdsa.PublicKey) error // EncryptRef encrypts a Swarm reference for a given grantee EncryptRef(ctx context.Context, storage kvs.KeyValueStore, grantee *ecdsa.PublicKey, ref swarm.Address) (swarm.Address, error) } @@ -44,13 +44,6 @@ type ActLogic struct { var _ Control = (*ActLogic)(nil) -// AddPublisher adds a new publisher to an empty act. -func (al ActLogic) AddPublisher(ctx context.Context, storage kvs.KeyValueStore, publisher *ecdsa.PublicKey) error { - accessKey := encryption.GenerateRandomKey(encryption.KeyLength) - - return al.AddGrantee(ctx, storage, publisher, publisher, &accessKey) -} - // EncryptRef encrypts a SWARM reference for a publisher. func (al ActLogic) EncryptRef(ctx context.Context, storage kvs.KeyValueStore, publisherPubKey *ecdsa.PublicKey, ref swarm.Address) (swarm.Address, error) { accessKey, err := al.getAccessKey(ctx, storage, publisherPubKey) @@ -67,21 +60,21 @@ func (al ActLogic) EncryptRef(ctx context.Context, storage kvs.KeyValueStore, pu } // AddGrantee adds a new grantee to the ACT. -func (al ActLogic) AddGrantee(ctx context.Context, storage kvs.KeyValueStore, publisherPubKey, granteePubKey *ecdsa.PublicKey, accessKeyPointer *encryption.Key) error { +func (al ActLogic) AddGrantee(ctx context.Context, storage kvs.KeyValueStore, publisherPubKey, granteePubKey *ecdsa.PublicKey) error { var ( accessKey encryption.Key err error ) - if accessKeyPointer == nil { + // Create new access key because grantee is the publisher + if publisherPubKey.Equal(granteePubKey) { + accessKey = encryption.GenerateRandomKey(encryption.KeyLength) + } else { // Get previously generated access key accessKey, err = al.getAccessKey(ctx, storage, publisherPubKey) if err != nil { return err } - } else { - // This is a newly created access key, because grantee is publisher (they are the same) - accessKey = *accessKeyPointer } // Encrypt the access key for the new Grantee diff --git a/pkg/dynamicaccess/accesslogic_test.go b/pkg/dynamicaccess/accesslogic_test.go index 93b65f1bdaf..b8cab2f8fbf 100644 --- a/pkg/dynamicaccess/accesslogic_test.go +++ b/pkg/dynamicaccess/accesslogic_test.go @@ -56,50 +56,48 @@ func getPrivKey(keyNumber int) *ecdsa.PrivateKey { } func TestDecryptRef_Success(t *testing.T) { + t.Parallel() ctx := context.Background() - id0 := getPrivKey(0) + id1 := getPrivKey(1) s := kvsmock.New() al := setupAccessLogic() - err := al.AddPublisher(ctx, s, &id0.PublicKey) + err := al.AddGrantee(ctx, s, &id1.PublicKey, &id1.PublicKey) if err != nil { - t.Fatalf("AddPublisher: expected no error, got %v", err) + t.Fatalf("AddGrantee: expected no error, got %v", err) } byteRef, _ := hex.DecodeString("39a5ea87b141fe44aa609c3327ecd896c0e2122897f5f4bbacf74db1033c5559") - expectedRef := swarm.NewAddress(byteRef) - t.Logf("encryptedRef: %s", expectedRef.String()) - - encryptedRef, err := al.EncryptRef(ctx, s, &id0.PublicKey, expectedRef) - t.Logf("encryptedRef: %s", encryptedRef.String()) + encryptedRef, err := al.EncryptRef(ctx, s, &id1.PublicKey, expectedRef) if err != nil { t.Fatalf("There was an error while calling EncryptRef: %v", err) } - actualRef, err := al.DecryptRef(ctx, s, encryptedRef, &id0.PublicKey) + actualRef, err := al.DecryptRef(ctx, s, encryptedRef, &id1.PublicKey) if err != nil { t.Fatalf("There was an error while calling Get: %v", err) } - if expectedRef.Compare(actualRef) != 0 { - t.Fatalf("Get gave back wrong Swarm reference!") + if !expectedRef.Equal(actualRef) { + t.Fatalf("DecryptRef gave back wrong Swarm reference! Expedted: %v, actual: %v", expectedRef, actualRef) } } func TestDecryptRefWithGrantee_Success(t *testing.T) { + t.Parallel() ctx := context.Background() id0, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) diffieHellman := dynamicaccess.NewDefaultSession(id0) al := dynamicaccess.NewLogic(diffieHellman) s := kvsmock.New() - err := al.AddPublisher(ctx, s, &id0.PublicKey) + err := al.AddGrantee(ctx, s, &id0.PublicKey, &id0.PublicKey) if err != nil { - t.Fatalf("AddPublisher: expected no error, got %v", err) + t.Fatalf("AddGrantee: expected no error, got %v", err) } id1, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - err = al.AddGrantee(ctx, s, &id0.PublicKey, &id1.PublicKey, nil) + err = al.AddGrantee(ctx, s, &id0.PublicKey, &id1.PublicKey) if err != nil { t.Fatalf("AddNewGrantee: expected no error, got %v", err) } @@ -107,10 +105,8 @@ func TestDecryptRefWithGrantee_Success(t *testing.T) { byteRef, _ := hex.DecodeString("39a5ea87b141fe44aa609c3327ecd896c0e2122897f5f4bbacf74db1033c5559") expectedRef := swarm.NewAddress(byteRef) - t.Logf("encryptedRef: %s", expectedRef.String()) encryptedRef, err := al.EncryptRef(ctx, s, &id0.PublicKey, expectedRef) - t.Logf("encryptedRef: %s", encryptedRef.String()) if err != nil { t.Fatalf("There was an error while calling EncryptRef: %v", err) } @@ -122,18 +118,19 @@ func TestDecryptRefWithGrantee_Success(t *testing.T) { t.Fatalf("There was an error while calling Get: %v", err) } - if expectedRef.Compare(actualRef) != 0 { - t.Fatalf("Get gave back wrong Swarm reference!") + if !expectedRef.Equal(actualRef) { + t.Fatalf("DecryptRef gave back wrong Swarm reference! Expedted: %v, actual: %v", expectedRef, actualRef) } } func TestDecryptRef_Error(t *testing.T) { + t.Parallel() id0 := getPrivKey(0) ctx := context.Background() s := kvsmock.New() al := setupAccessLogic() - err := al.AddPublisher(ctx, s, &id0.PublicKey) + err := al.AddGrantee(ctx, s, &id0.PublicKey, &id0.PublicKey) assert.NoError(t, err) expectedRef := "39a5ea87b141fe44aa609c3327ecd896c0e2122897f5f4bbacf74db1033c5559" @@ -148,13 +145,14 @@ func TestDecryptRef_Error(t *testing.T) { } func TestAddPublisher(t *testing.T) { + t.Parallel() id0 := getPrivKey(0) savedLookupKey := "b6ee086390c280eeb9824c331a4427596f0c8510d5564bc1b6168d0059a46e2b" s := kvsmock.New() ctx := context.Background() al := setupAccessLogic() - err := al.AddPublisher(ctx, s, &id0.PublicKey) + err := al.AddGrantee(ctx, s, &id0.PublicKey, &id0.PublicKey) assert.NoError(t, err) decodedSavedLookupKey, err := hex.DecodeString(savedLookupKey) @@ -168,14 +166,15 @@ func TestAddPublisher(t *testing.T) { // A random value is returned, so it is only possible to check the length of the returned value // We know the lookup key because the generated private key is fixed if len(decodedEncryptedAccessKey) != 64 { - t.Fatalf("AddPublisher: expected encrypted access key length 64, got %d", len(decodedEncryptedAccessKey)) + t.Fatalf("AddGrantee: expected encrypted access key length 64, got %d", len(decodedEncryptedAccessKey)) } if s == nil { - t.Fatalf("AddPublisher: expected act, got nil") + t.Fatalf("AddGrantee: expected act, got nil") } } func TestAddNewGranteeToContent(t *testing.T) { + t.Parallel() id0 := getPrivKey(0) id1 := getPrivKey(1) id2 := getPrivKey(2) @@ -187,13 +186,13 @@ func TestAddNewGranteeToContent(t *testing.T) { s := kvsmock.New() al := setupAccessLogic() - err := al.AddPublisher(ctx, s, &id0.PublicKey) + err := al.AddGrantee(ctx, s, &id0.PublicKey, &id0.PublicKey) assert.NoError(t, err) - err = al.AddGrantee(ctx, s, &id0.PublicKey, &id1.PublicKey, nil) + err = al.AddGrantee(ctx, s, &id0.PublicKey, &id1.PublicKey) assert.NoError(t, err) - err = al.AddGrantee(ctx, s, &id0.PublicKey, &id2.PublicKey, nil) + err = al.AddGrantee(ctx, s, &id0.PublicKey, &id2.PublicKey) assert.NoError(t, err) lookupKeyAsByte, err := hex.DecodeString(publisherLookupKey) diff --git a/pkg/dynamicaccess/controller.go b/pkg/dynamicaccess/controller.go index 2b3c9e24ace..030e7c3316d 100644 --- a/pkg/dynamicaccess/controller.go +++ b/pkg/dynamicaccess/controller.go @@ -86,7 +86,7 @@ func (c *ControllerStruct) UploadHandler( if err != nil { return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err } - err = c.accessLogic.AddPublisher(ctx, storage, publisher) + err = c.accessLogic.AddGrantee(ctx, storage, publisher, publisher) if err != nil { return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err } @@ -168,7 +168,7 @@ func (c *ControllerStruct) UpdateHandler( if err != nil { return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err } - err = c.accessLogic.AddPublisher(ctx, act, publisher) + err = c.accessLogic.AddGrantee(ctx, act, publisher, publisher) if err != nil { return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err } @@ -211,7 +211,7 @@ func (c *ControllerStruct) UpdateHandler( if err != nil { return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err } - err = c.accessLogic.AddPublisher(ctx, act, publisher) + err = c.accessLogic.AddGrantee(ctx, act, publisher, publisher) if err != nil { return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err } @@ -221,7 +221,7 @@ func (c *ControllerStruct) UpdateHandler( } for _, grantee := range granteesToAdd { - err := c.accessLogic.AddGrantee(ctx, act, publisher, grantee, nil) + err := c.accessLogic.AddGrantee(ctx, act, publisher, grantee) if err != nil { return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err } diff --git a/pkg/dynamicaccess/controller_test.go b/pkg/dynamicaccess/controller_test.go index 6aa7757a5ca..7118368afa8 100644 --- a/pkg/dynamicaccess/controller_test.go +++ b/pkg/dynamicaccess/controller_test.go @@ -32,15 +32,15 @@ func getHistoryFixture(ctx context.Context, ls file.LoadSaver, al dynamicaccess. pk2 := getPrivKey(2) kvs0, _ := kvs.New(ls) - al.AddPublisher(ctx, kvs0, publisher) + al.AddGrantee(ctx, kvs0, publisher, publisher) kvs0Ref, _ := kvs0.Save(ctx) kvs1, _ := kvs.New(ls) - al.AddPublisher(ctx, kvs1, publisher) - al.AddGrantee(ctx, kvs1, publisher, &pk1.PublicKey, nil) + al.AddGrantee(ctx, kvs1, publisher, publisher) + al.AddGrantee(ctx, kvs1, publisher, &pk1.PublicKey) kvs1Ref, _ := kvs1.Save(ctx) kvs2, _ := kvs.New(ls) - al.AddPublisher(ctx, kvs2, publisher) - al.AddGrantee(ctx, kvs2, publisher, &pk2.PublicKey, nil) + al.AddGrantee(ctx, kvs2, publisher, publisher) + al.AddGrantee(ctx, kvs2, publisher, &pk2.PublicKey) kvs2Ref, _ := kvs2.Save(ctx) firstTime := time.Date(1994, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() secondTime := time.Date(2000, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() @@ -53,6 +53,7 @@ func getHistoryFixture(ctx context.Context, ls file.LoadSaver, al dynamicaccess. } func TestController_UploadHandler(t *testing.T) { + t.Parallel() ctx := context.Background() publisher := getPrivKey(0) diffieHellman := dynamicaccess.NewDefaultSession(publisher) @@ -101,6 +102,7 @@ func TestController_UploadHandler(t *testing.T) { } func TestController_PublisherDownload(t *testing.T) { + t.Parallel() ctx := context.Background() publisher := getPrivKey(0) diffieHellman := dynamicaccess.NewDefaultSession(publisher) @@ -122,6 +124,7 @@ func TestController_PublisherDownload(t *testing.T) { } func TestController_GranteeDownload(t *testing.T) { + t.Parallel() ctx := context.Background() publisher := getPrivKey(0) grantee := getPrivKey(2) @@ -147,7 +150,8 @@ func TestController_GranteeDownload(t *testing.T) { assert.Equal(t, ref, dref) } -func TestController_HandleGrantees(t *testing.T) { +func TestController_UpdateHandler(t *testing.T) { + t.Parallel() ctx := context.Background() publisher := getPrivKey(1) diffieHellman := dynamicaccess.NewDefaultSession(publisher) @@ -223,7 +227,8 @@ func TestController_HandleGrantees(t *testing.T) { }) } -func TestController_GetGrantees(t *testing.T) { +func TestController_Get(t *testing.T) { + t.Parallel() ctx := context.Background() publisher := getPrivKey(1) caller := getPrivKey(0) diff --git a/pkg/dynamicaccess/grantee_test.go b/pkg/dynamicaccess/grantee_test.go index 91e44569b2a..f9a9b0904f8 100644 --- a/pkg/dynamicaccess/grantee_test.go +++ b/pkg/dynamicaccess/grantee_test.go @@ -52,6 +52,7 @@ func generateKeyListFixture() ([]*ecdsa.PublicKey, error) { } func TestGranteeAddGet(t *testing.T) { + t.Parallel() gl, _ := dynamicaccess.NewGranteeList(createLs()) keys, err := generateKeyListFixture() if err != nil { @@ -116,6 +117,7 @@ func TestGranteeAddGet(t *testing.T) { } func TestGranteeRemove(t *testing.T) { + t.Parallel() gl, _ := dynamicaccess.NewGranteeList(createLs()) keys, err := generateKeyListFixture() if err != nil { @@ -163,6 +165,7 @@ func TestGranteeRemove(t *testing.T) { } func TestGranteeSave(t *testing.T) { + t.Parallel() ctx := context.Background() keys, err := generateKeyListFixture() if err != nil { diff --git a/pkg/dynamicaccess/history_test.go b/pkg/dynamicaccess/history_test.go index edc86dc7f22..871b5241eab 100644 --- a/pkg/dynamicaccess/history_test.go +++ b/pkg/dynamicaccess/history_test.go @@ -21,6 +21,7 @@ import ( ) func TestHistoryAdd(t *testing.T) { + t.Parallel() h, err := dynamicaccess.NewHistory(nil) assert.NoError(t, err) @@ -33,6 +34,7 @@ func TestHistoryAdd(t *testing.T) { } func TestSingleNodeHistoryLookup(t *testing.T) { + t.Parallel() storer := mockstorer.New() ctx := context.Background() ls := loadsave.New(storer.ChunkStore(), storer.Cache(), pipelineFactory(storer.Cache(), false)) @@ -56,6 +58,7 @@ func TestSingleNodeHistoryLookup(t *testing.T) { } func TestMultiNodeHistoryLookup(t *testing.T) { + t.Parallel() storer := mockstorer.New() ctx := context.Background() ls := loadsave.New(storer.ChunkStore(), storer.Cache(), pipelineFactory(storer.Cache(), false)) @@ -121,6 +124,7 @@ func TestMultiNodeHistoryLookup(t *testing.T) { } func TestHistoryStore(t *testing.T) { + t.Parallel() storer := mockstorer.New() ctx := context.Background() ls := loadsave.New(storer.ChunkStore(), storer.Cache(), pipelineFactory(storer.Cache(), false)) diff --git a/pkg/kvs/kvs_test.go b/pkg/kvs/kvs_test.go index e2da7aa7030..2f9af999a95 100644 --- a/pkg/kvs/kvs_test.go +++ b/pkg/kvs/kvs_test.go @@ -38,6 +38,7 @@ func keyValuePair(t *testing.T) ([]byte, []byte) { } func TestKvs(t *testing.T) { + t.Parallel() s, err := kvs.New(createLs()) assert.NoError(t, err) @@ -119,6 +120,7 @@ func TestKvs(t *testing.T) { } func TestKvs_Save(t *testing.T) { + t.Parallel() ctx := context.Background() key1, val1 := keyValuePair(t) From b0dba7e321f4bc5691e1a49ac4ea1b10ced7c71c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Ar=C3=A1nyi?= Date: Wed, 22 May 2024 12:58:57 +0400 Subject: [PATCH 30/34] docs(act): add act api to openapi (#44) docs(act): add incoming act headers to openapi docs(act): add act headers to upload endpoints in openapi docs(act): add act grantee list endpoints to openapi --- openapi/Swarm.yaml | 165 +++++++++++++++++++++++++++++++++++++++ openapi/SwarmCommon.yaml | 70 +++++++++++++++++ openapi/SwarmDebug.yaml | 3 + 3 files changed, 238 insertions(+) diff --git a/openapi/Swarm.yaml b/openapi/Swarm.yaml index 7bcd224a7d0..d4024534449 100644 --- a/openapi/Swarm.yaml +++ b/openapi/Swarm.yaml @@ -89,6 +89,135 @@ paths: default: description: Default response + "/grantee": + post: + summary: "Create grantee list" + tags: + - ACT + parameters: + - in: header + schema: + $ref: "SwarmCommon.yaml#/components/parameters/SwarmPostageBatchId" + name: swarm-postage-batch-id + required: true + - in: header + schema: + $ref: "SwarmCommon.yaml#/components/parameters/SwarmTagParameter" + name: swarm-tag + required: false + - in: header + schema: + $ref: "SwarmCommon.yaml#/components/parameters/SwarmPinParameter" + name: swarm-pin + required: false + - in: header + schema: + $ref: "SwarmCommon.yaml#/components/parameters/SwarmDeferredUpload" + name: swarm-deferred-upload + required: false + - in: header + schema: + $ref: "SwarmCommon.yaml#/components/parameters/SwarmActHistoryAddress" + name: swarm-act-history-address + required: false + requestBody: + required: true + content: + application/json: + schema: + $ref: "SwarmCommon.yaml#/components/schemas/ActGranteesCreateRequest" + responses: + "201": + description: Ok + content: + application/json: + schema: + $ref: "SwarmCommon.yaml#/components/schemas/ActGranteesOperationResponse" + "400": + $ref: "SwarmCommon.yaml#/components/responses/400" + "500": + $ref: "SwarmCommon.yaml#/components/responses/500" + + "/grantee/{reference}": + get: + summary: "Get grantee list" + tags: + - ACT + parameters: + - in: path + name: reference + schema: + $ref: "SwarmCommon.yaml#/components/schemas/SwarmEncryptedReference" + required: true + description: Grantee list reference + responses: + "200": + description: Ok + content: + application/json: + schema: + type: array + items: + $ref: "SwarmCommon.yaml#/components/schemas/PublicKey" + "404": + $ref: "SwarmCommon.yaml#/components/responses/404" + "500": + $ref: "SwarmCommon.yaml#/components/responses/500" + patch: + summary: "Update grantee list" + description: "Add or remove grantees from an existing grantee list" + tags: + - ACT + parameters: + - in: path + name: reference + schema: + $ref: "SwarmCommon.yaml#/components/schemas/SwarmEncryptedReference" + required: true + description: Grantee list reference + - in: header + schema: + $ref: "SwarmCommon.yaml#/components/parameters/SwarmActHistoryAddress" + name: swarm-act-history-address + required: true + - in: header + schema: + $ref: "SwarmCommon.yaml#/components/parameters/SwarmPostageBatchId" + name: swarm-postage-batch-id + required: true + - in: header + schema: + $ref: "SwarmCommon.yaml#/components/parameters/SwarmTagParameter" + name: swarm-tag + required: false + - in: header + schema: + $ref: "SwarmCommon.yaml#/components/parameters/SwarmPinParameter" + name: swarm-pin + required: false + - in: header + schema: + $ref: "SwarmCommon.yaml#/components/parameters/SwarmDeferredUpload" + name: swarm-deferred-upload + required: false + requestBody: + required: true + content: + application/json: + schema: + $ref: "SwarmCommon.yaml#/components/schemas/ActGranteesPatchRequest" + responses: + "200": + description: Ok + content: + application/json: + schema: + $ref: "SwarmCommon.yaml#/components/schemas/ActGranteesOperationResponse" + "400": + $ref: "SwarmCommon.yaml#/components/responses/400" + "500": + $ref: "SwarmCommon.yaml#/components/responses/500" + "/bytes": post: summary: "Upload data" @@ -167,6 +296,9 @@ paths: - $ref: "SwarmCommon.yaml#/components/parameters/SwarmRedundancyStrategyParameter" - $ref: "SwarmCommon.yaml#/components/parameters/SwarmRedundancyFallbackModeParameter" - $ref: "SwarmCommon.yaml#/components/parameters/SwarmChunkRetrievalTimeoutParameter" + - $ref: "SwarmCommon.yaml#/components/parameters/SwarmActTimestamp" + - $ref: "SwarmCommon.yaml#/components/parameters/SwarmActPublisher" + - $ref: "SwarmCommon.yaml#/components/parameters/SwarmActHistoryAddress" responses: "200": description: Retrieved content specified by reference @@ -190,6 +322,9 @@ paths: $ref: "SwarmCommon.yaml#/components/schemas/SwarmAddress" required: true description: Swarm address of chunk + - $ref: "SwarmCommon.yaml#/components/parameters/SwarmActTimestamp" + - $ref: "SwarmCommon.yaml#/components/parameters/SwarmActPublisher" + - $ref: "SwarmCommon.yaml#/components/parameters/SwarmActHistoryAddress" responses: "200": description: Chunk exists @@ -208,6 +343,8 @@ paths: parameters: - $ref: "SwarmCommon.yaml#/components/parameters/SwarmTagParameter" - $ref: "SwarmCommon.yaml#/components/parameters/SwarmPostageBatchId" + - $ref: "SwarmCommon.yaml#/components/parameters/SwarmAct" + - $ref: "SwarmCommon.yaml#/components/parameters/SwarmActHistoryAddress" requestBody: description: Chunk binary data that has to have at least 8 bytes. content: @@ -223,6 +360,8 @@ paths: description: Tag UID if it was passed to the request `swarm-tag` header. schema: $ref: "SwarmCommon.yaml#/components/schemas/Uid" + "swarm-act-history-address": + $ref: "SwarmCommon.yaml#/components/headers/SwarmActHistoryAddress" content: application/json: schema: @@ -279,6 +418,8 @@ paths: - $ref: "SwarmCommon.yaml#/components/parameters/SwarmPostageBatchId" - $ref: "SwarmCommon.yaml#/components/parameters/SwarmDeferredUpload" - $ref: "SwarmCommon.yaml#/components/parameters/SwarmRedundancyLevelParameter" + - $ref: "SwarmCommon.yaml#/components/parameters/SwarmAct" + - $ref: "SwarmCommon.yaml#/components/parameters/SwarmActHistoryAddress" requestBody: content: multipart/form-data: @@ -305,6 +446,8 @@ paths: $ref: "SwarmCommon.yaml#/components/headers/SwarmTag" "etag": $ref: "SwarmCommon.yaml#/components/headers/ETag" + "swarm-act-history-address": + $ref: "SwarmCommon.yaml#/components/headers/SwarmActHistoryAddress" content: application/json: schema: @@ -334,6 +477,9 @@ paths: - $ref: "SwarmCommon.yaml#/components/parameters/SwarmRedundancyStrategyParameter" - $ref: "SwarmCommon.yaml#/components/parameters/SwarmRedundancyFallbackModeParameter" - $ref: "SwarmCommon.yaml#/components/parameters/SwarmChunkRetrievalTimeoutParameter" + - $ref: "SwarmCommon.yaml#/components/parameters/SwarmActTimestamp" + - $ref: "SwarmCommon.yaml#/components/parameters/SwarmActPublisher" + - $ref: "SwarmCommon.yaml#/components/parameters/SwarmActHistoryAddress" responses: "200": description: Ok @@ -363,6 +509,9 @@ paths: $ref: "SwarmCommon.yaml#/components/schemas/SwarmAddress" required: true description: Swarm address of chunk + - $ref: "SwarmCommon.yaml#/components/parameters/SwarmActTimestamp" + - $ref: "SwarmCommon.yaml#/components/parameters/SwarmActPublisher" + - $ref: "SwarmCommon.yaml#/components/parameters/SwarmActHistoryAddress" responses: "200": description: Chunk exists @@ -749,6 +898,8 @@ paths: $ref: "SwarmCommon.yaml#/components/parameters/SwarmPostageBatchId" name: swarm-postage-batch-id required: true + - $ref: "SwarmCommon.yaml#/components/parameters/SwarmAct" + - $ref: "SwarmCommon.yaml#/components/parameters/SwarmActHistoryAddress" requestBody: required: true description: The SOC binary data is composed of the span (8 bytes) and the at most 4KB payload. @@ -764,6 +915,9 @@ paths: application/json: schema: $ref: "SwarmCommon.yaml#/components/schemas/ReferenceResponse" + headers: + "swarm-act-history-address": + $ref: "SwarmCommon.yaml#/components/headers/SwarmActHistoryAddress" "400": $ref: "SwarmCommon.yaml#/components/responses/400" "401": @@ -801,6 +955,8 @@ paths: description: "Feed indexing scheme (default: sequence)" - $ref: "SwarmCommon.yaml#/components/parameters/SwarmPinParameter" - $ref: "SwarmCommon.yaml#/components/parameters/SwarmPostageBatchId" + - $ref: "SwarmCommon.yaml#/components/parameters/SwarmAct" + - $ref: "SwarmCommon.yaml#/components/parameters/SwarmActHistoryAddress" responses: "201": description: Created @@ -808,6 +964,9 @@ paths: application/json: schema: $ref: "SwarmCommon.yaml#/components/schemas/ReferenceResponse" + headers: + "swarm-act-history-address": + $ref: "SwarmCommon.yaml#/components/headers/SwarmActHistoryAddress" "400": $ref: "SwarmCommon.yaml#/components/responses/400" "401": @@ -1150,6 +1309,9 @@ paths: $ref: "SwarmCommon.yaml#/components/parameters/SwarmCache" name: swarm-cache required: false + - $ref: "SwarmCommon.yaml#/components/parameters/SwarmActTimestamp" + - $ref: "SwarmCommon.yaml#/components/parameters/SwarmActPublisher" + - $ref: "SwarmCommon.yaml#/components/parameters/SwarmActHistoryAddress" responses: "200": description: Retrieved chunk content @@ -1177,6 +1339,9 @@ paths: $ref: "SwarmCommon.yaml#/components/schemas/SwarmAddress" required: true description: Swarm address of chunk + - $ref: "SwarmCommon.yaml#/components/parameters/SwarmActTimestamp" + - $ref: "SwarmCommon.yaml#/components/parameters/SwarmActPublisher" + - $ref: "SwarmCommon.yaml#/components/parameters/SwarmActHistoryAddress" responses: "200": description: Chunk exists diff --git a/openapi/SwarmCommon.yaml b/openapi/SwarmCommon.yaml index 84d690fc64f..85b7b2d6f62 100644 --- a/openapi/SwarmCommon.yaml +++ b/openapi/SwarmCommon.yaml @@ -87,6 +87,36 @@ components: ghostBalance: $ref: "#/components/schemas/BigInt" + ActGranteesCreateRequest: + type: object + properties: + grantees: + type: array + items: + $ref: "#/components/schemas/PublicKey" + + ActGranteesPatchRequest: + type: object + properties: + add: + type: array + items: + $ref: "#/components/schemas/PublicKey" + description: List of grantees to add + revoke: + type: array + items: + $ref: "#/components/schemas/PublicKey" + description: List of grantees to revoke future access from + + ActGranteesOperationResponse: + type: object + properties: + ref: + $ref: "#/components/schemas/SwarmEncryptedReference" + historyref: + $ref: "#/components/schemas/SwarmEncryptedReference" + Balance: type: object properties: @@ -977,6 +1007,12 @@ components: schema: $ref: "#/components/schemas/HexString" + SwarmActHistoryAddress: + description: "Swarm address reference to the new ACT history entry" + schema: + $ref: "#/components/schemas/SwarmAddress" + required: false + ETag: description: | The RFC7232 ETag header field in a response provides the current entity- @@ -1137,6 +1173,40 @@ components: required: false description: "Determines if the download data should be cached on the node. By default the download will be cached" + SwarmAct: + in: header + name: swarm-act + schema: + type: boolean + default: "false" + required: false + description: "Determines if the uploaded data should be treated as ACT content" + + SwarmActPublisher: + in: header + name: swarm-act-publisher + schema: + $ref: "#/components/schemas/PublicKey" + required: false + description: "ACT content publisher's public key" + + SwarmActHistoryAddress: + in: header + name: swarm-act-history-address + schema: + $ref: "#/components/schemas/SwarmAddress" + required: false + description: "ACT history reference address" + + SwarmActTimestamp: + in: header + name: swarm-act-timestamp + schema: + type: integer + format: int64 + required: false + description: "ACT history Unix timestamp" + responses: "200": description: OK. diff --git a/openapi/SwarmDebug.yaml b/openapi/SwarmDebug.yaml index e2091171729..ff51a96300a 100644 --- a/openapi/SwarmDebug.yaml +++ b/openapi/SwarmDebug.yaml @@ -201,6 +201,9 @@ paths: $ref: "SwarmCommon.yaml#/components/schemas/SwarmAddress" required: true description: Swarm address of chunk + - $ref: "SwarmCommon.yaml#/components/parameters/SwarmActTimestamp" + - $ref: "SwarmCommon.yaml#/components/parameters/SwarmActPublisher" + - $ref: "SwarmCommon.yaml#/components/parameters/SwarmActHistoryAddress" responses: "200": description: Chunk exists From 5b19d0e1557d09a9ee3a5f9f907351fdabfa0de0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A1lint=20Ujv=C3=A1ri?= <58116288+bosi95@users.noreply.github.com> Date: Thu, 23 May 2024 14:19:55 +0200 Subject: [PATCH 31/34] refactor(act): chunk download and granteelist handling (#46) --- pkg/api/dynamicaccess.go | 12 +++++++----- pkg/dynamicaccess/controller.go | 15 ++++----------- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/pkg/api/dynamicaccess.go b/pkg/api/dynamicaccess.go index e776857d190..aad9a69d56a 100644 --- a/pkg/api/dynamicaccess.go +++ b/pkg/api/dynamicaccess.go @@ -113,6 +113,8 @@ func (s *Service) actDecryptionHandler() func(h http.Handler) http.Handler { ls := loadsave.NewReadonly(s.storer.Download(cache)) reference, err := s.dac.DownloadHandler(ctx, ls, paths.Address, headers.Publisher, *headers.HistoryAddress, timestamp) if err != nil { + logger.Debug("act failed to decrypt reference", "error", err) + logger.Error(nil, "act failed to decrypt reference") jsonhttp.InternalServerError(w, errActDownload) return } @@ -132,7 +134,7 @@ func (s *Service) actEncryptionHandler( ) (swarm.Address, error) { logger := s.logger.WithName("act_encryption_handler").Build() publisherPublicKey := &s.publicKey - ls := loadsave.New(s.storer.ChunkStore(), s.storer.Cache(), requestPipelineFactory(ctx, putter, false, redundancy.NONE)) + ls := loadsave.New(s.storer.Download(true), s.storer.Cache(), requestPipelineFactory(ctx, putter, false, redundancy.NONE)) storageReference, historyReference, encryptedReference, err := s.dac.UploadHandler(ctx, ls, reference, publisherPublicKey, historyRootHash) if err != nil { logger.Debug("act failed to encrypt reference", "error", err) @@ -324,8 +326,8 @@ func (s *Service) actGrantRevokeHandler(w http.ResponseWriter, r *http.Request) granteeref := paths.GranteesAddress publisher := &s.publicKey - ls := loadsave.New(s.storer.ChunkStore(), s.storer.Cache(), requestPipelineFactory(ctx, putter, false, redundancy.NONE)) - gls := loadsave.New(s.storer.ChunkStore(), s.storer.Cache(), requestPipelineFactory(ctx, putter, granteeListEncrypt, redundancy.NONE)) + ls := loadsave.New(s.storer.Download(true), s.storer.Cache(), requestPipelineFactory(ctx, putter, false, redundancy.NONE)) + gls := loadsave.New(s.storer.Download(true), s.storer.Cache(), requestPipelineFactory(ctx, putter, granteeListEncrypt, redundancy.NONE)) granteeref, encryptedglref, historyref, actref, err := s.dac.UpdateHandler(ctx, ls, gls, granteeref, historyAddress, publisher, grantees.Addlist, grantees.Revokelist) if err != nil { logger.Debug("failed to update grantee list", "error", err) @@ -469,8 +471,8 @@ func (s *Service) actCreateGranteesHandler(w http.ResponseWriter, r *http.Reques } publisher := &s.publicKey - ls := loadsave.New(s.storer.ChunkStore(), s.storer.Cache(), requestPipelineFactory(ctx, putter, false, redundancy.NONE)) - gls := loadsave.New(s.storer.ChunkStore(), s.storer.Cache(), requestPipelineFactory(ctx, putter, granteeListEncrypt, redundancy.NONE)) + ls := loadsave.New(s.storer.Download(true), s.storer.Cache(), requestPipelineFactory(ctx, putter, false, redundancy.NONE)) + gls := loadsave.New(s.storer.Download(true), s.storer.Cache(), requestPipelineFactory(ctx, putter, granteeListEncrypt, redundancy.NONE)) granteeref, encryptedglref, historyref, actref, err := s.dac.UpdateHandler(ctx, ls, gls, swarm.ZeroAddress, historyAddress, publisher, list, nil) if err != nil { logger.Debug("failed to update grantee list", "error", err) diff --git a/pkg/dynamicaccess/controller.go b/pkg/dynamicaccess/controller.go index 030e7c3316d..7ce650ef348 100644 --- a/pkg/dynamicaccess/controller.go +++ b/pkg/dynamicaccess/controller.go @@ -76,7 +76,6 @@ func (c *ControllerStruct) UploadHandler( storage kvs.KeyValueStore actRef swarm.Address ) - now := time.Now().Unix() if historyRef.IsZero() { history, err := NewHistory(ls) if err != nil { @@ -94,7 +93,7 @@ func (c *ControllerStruct) UploadHandler( if err != nil { return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err } - err = history.Add(ctx, actRef, &now, nil) + err = history.Add(ctx, actRef, nil, nil) if err != nil { return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err } @@ -107,12 +106,11 @@ func (c *ControllerStruct) UploadHandler( if err != nil { return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err } - entry, err := history.Lookup(ctx, now) - actRef = entry.Reference() + entry, err := history.Lookup(ctx, time.Now().Unix()) if err != nil { return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err } - storage, err = kvs.NewReference(ls, actRef) + storage, err = kvs.NewReference(ls, entry.Reference()) if err != nil { return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err } @@ -197,15 +195,12 @@ func (c *ControllerStruct) UpdateHandler( return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err } } + granteesToAdd := addList if len(removeList) != 0 { err = gl.Remove(removeList) if err != nil { return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err } - } - - var granteesToAdd []*ecdsa.PublicKey - if len(removeList) != 0 || encryptedglref.IsZero() { // generate new access key and new act act, err = kvs.New(ls) if err != nil { @@ -216,8 +211,6 @@ func (c *ControllerStruct) UpdateHandler( return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err } granteesToAdd = gl.Get() - } else { - granteesToAdd = addList } for _, grantee := range granteesToAdd { From 3a1ff2bfc0bf3e03c7d7f911e4d6ecad1e0d8b1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A1lint=20Ujv=C3=A1ri?= <58116288+bosi95@users.noreply.github.com> Date: Fri, 24 May 2024 18:55:06 +0200 Subject: [PATCH 32/34] refactor(act): controller logic (#47) --- pkg/dynamicaccess/controller.go | 270 +++++++++++++++----------------- 1 file changed, 129 insertions(+), 141 deletions(-) diff --git a/pkg/dynamicaccess/controller.go b/pkg/dynamicaccess/controller.go index 7ce650ef348..48e771cddc5 100644 --- a/pkg/dynamicaccess/controller.go +++ b/pkg/dynamicaccess/controller.go @@ -19,18 +19,18 @@ import ( type Grantees interface { // UpdateHandler manages the grantees for the given publisher, updating the list based on provided public keys to add or remove. // Only the publisher can make changes to the grantee list. - UpdateHandler(ctx context.Context, ls file.LoadSaver, gls file.LoadSaver, granteeref swarm.Address, historyref swarm.Address, publisher *ecdsa.PublicKey, addList, removeList []*ecdsa.PublicKey) (swarm.Address, swarm.Address, swarm.Address, swarm.Address, error) + UpdateHandler(ctx context.Context, ls file.LoadSaver, gls file.LoadSaver, granteeRef swarm.Address, historyRef swarm.Address, publisher *ecdsa.PublicKey, addList, removeList []*ecdsa.PublicKey) (swarm.Address, swarm.Address, swarm.Address, swarm.Address, error) // Get returns the list of grantees for the given publisher. // The list is accessible only by the publisher. - Get(ctx context.Context, ls file.LoadSaver, publisher *ecdsa.PublicKey, encryptedglref swarm.Address) ([]*ecdsa.PublicKey, error) + Get(ctx context.Context, ls file.LoadSaver, publisher *ecdsa.PublicKey, encryptedglRef swarm.Address) ([]*ecdsa.PublicKey, error) } type Controller interface { Grantees // DownloadHandler decrypts the encryptedRef using the lookupkey based on the history and timestamp. - DownloadHandler(ctx context.Context, ls file.LoadSaver, encryptedRef swarm.Address, publisher *ecdsa.PublicKey, historyRootHash swarm.Address, timestamp int64) (swarm.Address, error) + DownloadHandler(ctx context.Context, ls file.LoadSaver, encryptedRef swarm.Address, publisher *ecdsa.PublicKey, historyRef swarm.Address, timestamp int64) (swarm.Address, error) // UploadHandler encrypts the reference and stores it in the history as the latest update. - UploadHandler(ctx context.Context, ls file.LoadSaver, reference swarm.Address, publisher *ecdsa.PublicKey, historyRootHash swarm.Address) (swarm.Address, swarm.Address, swarm.Address, error) + UploadHandler(ctx context.Context, ls file.LoadSaver, reference swarm.Address, publisher *ecdsa.PublicKey, historyRef swarm.Address) (swarm.Address, swarm.Address, swarm.Address, error) io.Closer } @@ -40,23 +40,21 @@ type ControllerStruct struct { var _ Controller = (*ControllerStruct)(nil) +func NewController(accessLogic ActLogic) *ControllerStruct { + return &ControllerStruct{ + accessLogic: accessLogic, + } +} + func (c *ControllerStruct) DownloadHandler( ctx context.Context, ls file.LoadSaver, encryptedRef swarm.Address, publisher *ecdsa.PublicKey, - historyRootHash swarm.Address, + historyRef swarm.Address, timestamp int64, ) (swarm.Address, error) { - history, err := NewHistoryReference(ls, historyRootHash) - if err != nil { - return swarm.ZeroAddress, err - } - entry, err := history.Lookup(ctx, timestamp) - if err != nil { - return swarm.ZeroAddress, err - } - act, err := kvs.NewReference(ls, entry.Reference()) + _, act, err := c.getHistoryAndAct(ctx, ls, historyRef, publisher, timestamp) if err != nil { return swarm.ZeroAddress, err } @@ -69,125 +67,43 @@ func (c *ControllerStruct) UploadHandler( ls file.LoadSaver, reference swarm.Address, publisher *ecdsa.PublicKey, - historyRootHash swarm.Address, + historyRef swarm.Address, ) (swarm.Address, swarm.Address, swarm.Address, error) { - historyRef := historyRootHash - var ( - storage kvs.KeyValueStore - actRef swarm.Address - ) + history, act, err := c.getHistoryAndAct(ctx, ls, historyRef, publisher, time.Now().Unix()) + if err != nil { + return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err + } + actRef := swarm.ZeroAddress + newHistoryRef := historyRef if historyRef.IsZero() { - history, err := NewHistory(ls) - if err != nil { - return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err - } - storage, err = kvs.New(ls) - if err != nil { - return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err - } - err = c.accessLogic.AddGrantee(ctx, storage, publisher, publisher) - if err != nil { - return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err - } - actRef, err = storage.Save(ctx) - if err != nil { - return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err - } - err = history.Add(ctx, actRef, nil, nil) - if err != nil { - return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err - } - historyRef, err = history.Store(ctx) - if err != nil { - return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err - } - } else { - history, err := NewHistoryReference(ls, historyRef) - if err != nil { - return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err - } - entry, err := history.Lookup(ctx, time.Now().Unix()) - if err != nil { - return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err - } - storage, err = kvs.NewReference(ls, entry.Reference()) + newHistoryRef, actRef, err = c.saveHistoryAndAct(ctx, history, nil, act) if err != nil { return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err } } - encryptedRef, err := c.accessLogic.EncryptRef(ctx, storage, publisher, reference) - return actRef, historyRef, encryptedRef, err -} - -func NewController(accessLogic ActLogic) *ControllerStruct { - return &ControllerStruct{ - accessLogic: accessLogic, - } + encryptedRef, err := c.accessLogic.EncryptRef(ctx, act, publisher, reference) + return actRef, newHistoryRef, encryptedRef, err } func (c *ControllerStruct) UpdateHandler( ctx context.Context, ls file.LoadSaver, gls file.LoadSaver, - encryptedglref swarm.Address, - historyref swarm.Address, + encryptedglRef swarm.Address, + historyRef swarm.Address, publisher *ecdsa.PublicKey, addList []*ecdsa.PublicKey, removeList []*ecdsa.PublicKey, ) (swarm.Address, swarm.Address, swarm.Address, swarm.Address, error) { - var ( - err error - h History - act kvs.KeyValueStore - granteeref swarm.Address - ) - if !historyref.IsZero() { - h, err = NewHistoryReference(ls, historyref) - if err != nil { - return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err - } - entry, err := h.Lookup(ctx, time.Now().Unix()) - if err != nil { - return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err - } - actref := entry.Reference() - act, err = kvs.NewReference(ls, actref) - if err != nil { - return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err - } - } else { - h, err = NewHistory(ls) - if err != nil { - return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err - } - // generate new access key and new act - act, err = kvs.New(ls) - if err != nil { - return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err - } - err = c.accessLogic.AddGrantee(ctx, act, publisher, publisher) - if err != nil { - return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err - } + history, act, err := c.getHistoryAndAct(ctx, ls, historyRef, publisher, time.Now().Unix()) + if err != nil { + return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err } - var gl GranteeList - if encryptedglref.IsZero() { - gl, err = NewGranteeList(gls) - if err != nil { - return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err - } - } else { - granteeref, err = c.decryptRefForPublisher(publisher, encryptedglref) - if err != nil { - return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err - } - - gl, err = NewGranteeListReference(ctx, gls, granteeref) - if err != nil { - return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err - } + gl, err := c.getGranteeList(ctx, gls, encryptedglRef, publisher) + if err != nil { + return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err } if len(addList) != 0 { err = gl.Add(addList) @@ -201,14 +117,12 @@ func (c *ControllerStruct) UpdateHandler( if err != nil { return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err } - // generate new access key and new act - act, err = kvs.New(ls) - if err != nil { - return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err - } - err = c.accessLogic.AddGrantee(ctx, act, publisher, publisher) - if err != nil { - return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err + // generate new access key and new act, only if history was not newly created + if !historyRef.IsZero() { + act, err = c.newActWithPublisher(ctx, ls, publisher) + if err != nil { + return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err + } } granteesToAdd = gl.Get() } @@ -220,51 +134,125 @@ func (c *ControllerStruct) UpdateHandler( } } - actref, err := act.Save(ctx) + granteeRef, err := gl.Save(ctx) if err != nil { return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err } - glref, err := gl.Save(ctx) - if err != nil { - return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err - } - - eglref, err := c.encryptRefForPublisher(publisher, glref) + egranteeRef, err := c.encryptRefForPublisher(publisher, granteeRef) if err != nil { return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err } // need to re-initialize history, because Lookup loads the forks causing the manifest save to skip the root node - if !historyref.IsZero() { - h, err = NewHistoryReference(ls, historyref) + if !historyRef.IsZero() { + history, err = NewHistoryReference(ls, historyRef) if err != nil { return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err } } - mtdt := map[string]string{"encryptedglref": eglref.String()} - err = h.Add(ctx, actref, nil, &mtdt) + mtdt := map[string]string{"encryptedglref": egranteeRef.String()} + hRef, actRef, err := c.saveHistoryAndAct(ctx, history, &mtdt, act) if err != nil { return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err } - href, err := h.Store(ctx) + + return granteeRef, egranteeRef, hRef, actRef, nil +} + +func (c *ControllerStruct) Get(ctx context.Context, ls file.LoadSaver, publisher *ecdsa.PublicKey, encryptedglRef swarm.Address) ([]*ecdsa.PublicKey, error) { + gl, err := c.getGranteeList(ctx, ls, encryptedglRef, publisher) if err != nil { - return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err + return nil, err } - - return glref, eglref, href, actref, nil + return gl.Get(), nil } -func (c *ControllerStruct) Get(ctx context.Context, ls file.LoadSaver, publisher *ecdsa.PublicKey, encryptedglref swarm.Address) ([]*ecdsa.PublicKey, error) { - granteeRef, err := c.decryptRefForPublisher(publisher, encryptedglref) +func (c *ControllerStruct) newActWithPublisher(ctx context.Context, ls file.LoadSaver, publisher *ecdsa.PublicKey) (kvs.KeyValueStore, error) { + act, err := kvs.New(ls) if err != nil { return nil, err } - gl, err := NewGranteeListReference(ctx, ls, granteeRef) + err = c.accessLogic.AddGrantee(ctx, act, publisher, publisher) if err != nil { return nil, err } - return gl.Get(), nil + + return act, nil +} +func (c *ControllerStruct) getHistoryAndAct(ctx context.Context, ls file.LoadSaver, historyRef swarm.Address, publisher *ecdsa.PublicKey, timestamp int64) (History, kvs.KeyValueStore, error) { + var ( + history History + act kvs.KeyValueStore + err error + ) + if historyRef.IsZero() { + history, err = NewHistory(ls) + if err != nil { + return nil, nil, err + } + act, err = c.newActWithPublisher(ctx, ls, publisher) + if err != nil { + return nil, nil, err + } + } else { + history, err = NewHistoryReference(ls, historyRef) + if err != nil { + return nil, nil, err + } + entry, err := history.Lookup(ctx, timestamp) + if err != nil { + return nil, nil, err + } + act, err = kvs.NewReference(ls, entry.Reference()) + if err != nil { + return nil, nil, err + } + } + + return history, act, nil +} + +func (c *ControllerStruct) saveHistoryAndAct(ctx context.Context, history History, mtdt *map[string]string, act kvs.KeyValueStore) (swarm.Address, swarm.Address, error) { + actRef, err := act.Save(ctx) + if err != nil { + return swarm.ZeroAddress, swarm.ZeroAddress, err + } + err = history.Add(ctx, actRef, nil, mtdt) + if err != nil { + return swarm.ZeroAddress, swarm.ZeroAddress, err + } + historyRef, err := history.Store(ctx) + if err != nil { + return swarm.ZeroAddress, swarm.ZeroAddress, err + } + + return historyRef, actRef, nil +} + +func (c *ControllerStruct) getGranteeList(ctx context.Context, ls file.LoadSaver, encryptedglRef swarm.Address, publisher *ecdsa.PublicKey) (GranteeList, error) { + var ( + gl GranteeList + err error + ) + if encryptedglRef.IsZero() { + gl, err = NewGranteeList(ls) + if err != nil { + return nil, err + } + } else { + granteeref, err := c.decryptRefForPublisher(publisher, encryptedglRef) + if err != nil { + return nil, err + } + + gl, err = NewGranteeListReference(ctx, ls, granteeref) + if err != nil { + return nil, err + } + } + + return gl, nil } func (c *ControllerStruct) encryptRefForPublisher(publisherPubKey *ecdsa.PublicKey, ref swarm.Address) (swarm.Address, error) { From 09a876b17b5e06af3ae4d98264f78167da0a4a1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A1lint=20Ujv=C3=A1ri?= <58116288+bosi95@users.noreply.github.com> Date: Wed, 29 May 2024 16:58:08 +0200 Subject: [PATCH 33/34] test(act): controller add, revoke and get with history + fix typos (#48) --- openapi/SwarmCommon.yaml | 2 +- pkg/api/dynamicaccess.go | 8 ++--- pkg/dynamicaccess/controller.go | 16 ++++------ pkg/dynamicaccess/controller_test.go | 44 ++++++++++++++++++++++++++-- pkg/dynamicaccess/mock/controller.go | 28 +++++++++--------- pkg/kvs/kvs.go | 10 +++---- 6 files changed, 71 insertions(+), 37 deletions(-) diff --git a/openapi/SwarmCommon.yaml b/openapi/SwarmCommon.yaml index 85b7b2d6f62..620f3ed8aff 100644 --- a/openapi/SwarmCommon.yaml +++ b/openapi/SwarmCommon.yaml @@ -883,7 +883,7 @@ components: reserveSize: type: integer reserveSizeWithinRadius: - type: interger + type: integer pullsyncRate: type: number storageRadius: diff --git a/pkg/api/dynamicaccess.go b/pkg/api/dynamicaccess.go index aad9a69d56a..f83f0cca435 100644 --- a/pkg/api/dynamicaccess.go +++ b/pkg/api/dynamicaccess.go @@ -281,23 +281,23 @@ func (s *Service) actGrantRevokeHandler(w http.ResponseWriter, r *http.Request) } grantees := GranteesPatch{} - paresAddlist, err := parseKeys(gpr.Addlist) + parsedAddlist, err := parseKeys(gpr.Addlist) if err != nil { logger.Debug("add list key parse failed", "error", err) logger.Error(nil, "add list key parse failed") jsonhttp.InternalServerError(w, "error add list key parsing") return } - grantees.Addlist = append(grantees.Addlist, paresAddlist...) + grantees.Addlist = append(grantees.Addlist, parsedAddlist...) - paresRevokelist, err := parseKeys(gpr.Revokelist) + parsedRevokelist, err := parseKeys(gpr.Revokelist) if err != nil { logger.Debug("revoke list key parse failed", "error", err) logger.Error(nil, "revoke list key parse failed") jsonhttp.InternalServerError(w, "error revoke list key parsing") return } - grantees.Revokelist = append(grantees.Revokelist, paresRevokelist...) + grantees.Revokelist = append(grantees.Revokelist, parsedRevokelist...) ctx := r.Context() putter, err := s.newStamperPutter(ctx, putterOptions{ diff --git a/pkg/dynamicaccess/controller.go b/pkg/dynamicaccess/controller.go index 48e771cddc5..dffe4bbdf64 100644 --- a/pkg/dynamicaccess/controller.go +++ b/pkg/dynamicaccess/controller.go @@ -86,6 +86,8 @@ func (c *ControllerStruct) UploadHandler( return actRef, newHistoryRef, encryptedRef, err } +// Limitation: If an upadate is called again within a second from the latest upload/update then mantaray save fails with ErrInvalidInput, +// because the key (timestamp) is already present, hence a new fork is not created func (c *ControllerStruct) UpdateHandler( ctx context.Context, ls file.LoadSaver, @@ -180,12 +182,8 @@ func (c *ControllerStruct) newActWithPublisher(ctx context.Context, ls file.Load return act, nil } -func (c *ControllerStruct) getHistoryAndAct(ctx context.Context, ls file.LoadSaver, historyRef swarm.Address, publisher *ecdsa.PublicKey, timestamp int64) (History, kvs.KeyValueStore, error) { - var ( - history History - act kvs.KeyValueStore - err error - ) + +func (c *ControllerStruct) getHistoryAndAct(ctx context.Context, ls file.LoadSaver, historyRef swarm.Address, publisher *ecdsa.PublicKey, timestamp int64) (history History, act kvs.KeyValueStore, err error) { if historyRef.IsZero() { history, err = NewHistory(ls) if err != nil { @@ -230,11 +228,7 @@ func (c *ControllerStruct) saveHistoryAndAct(ctx context.Context, history Histor return historyRef, actRef, nil } -func (c *ControllerStruct) getGranteeList(ctx context.Context, ls file.LoadSaver, encryptedglRef swarm.Address, publisher *ecdsa.PublicKey) (GranteeList, error) { - var ( - gl GranteeList - err error - ) +func (c *ControllerStruct) getGranteeList(ctx context.Context, ls file.LoadSaver, encryptedglRef swarm.Address, publisher *ecdsa.PublicKey) (gl GranteeList, err error) { if encryptedglRef.IsZero() { gl, err = NewGranteeList(ls) if err != nil { diff --git a/pkg/dynamicaccess/controller_test.go b/pkg/dynamicaccess/controller_test.go index 7118368afa8..c25d8079bfa 100644 --- a/pkg/dynamicaccess/controller_test.go +++ b/pkg/dynamicaccess/controller_test.go @@ -19,6 +19,7 @@ import ( "github.com/ethersphere/bee/v2/pkg/kvs" "github.com/ethersphere/bee/v2/pkg/swarm" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "golang.org/x/crypto/sha3" ) @@ -206,13 +207,52 @@ func TestController_UpdateHandler(t *testing.T) { assert.NoError(t, err) assert.Len(t, gl.Get(), 2) }) + t.Run("add and revoke then get from history", func(t *testing.T) { + addRevokeList := []*ecdsa.PublicKey{&grantee.PublicKey} + ref := swarm.RandAddress(t) + _, hRef, encRef, err := c.UploadHandler(ctx, ls, ref, &publisher.PublicKey, swarm.ZeroAddress) + require.NoError(t, err) + + // Need to wait a second before each update call so that a new history mantaray fork is created for the new key(timestamp) entry + time.Sleep(1 * time.Second) + beforeRevokeTS := time.Now().Unix() + _, egranteeRef, hrefUpdate1, _, err := c.UpdateHandler(ctx, ls, gls, swarm.ZeroAddress, hRef, &publisher.PublicKey, addRevokeList, nil) + require.NoError(t, err) + + time.Sleep(1 * time.Second) + granteeRef, _, hrefUpdate2, _, err := c.UpdateHandler(ctx, ls, gls, egranteeRef, hrefUpdate1, &publisher.PublicKey, nil, addRevokeList) + require.NoError(t, err) + gl, err := dynamicaccess.NewGranteeListReference(ctx, ls, granteeRef) + require.NoError(t, err) + assert.Empty(t, gl.Get()) + // expect history reference to be different after grantee list update + assert.NotEqual(t, hrefUpdate1, hrefUpdate2) + + granteeDH := dynamicaccess.NewDefaultSession(grantee) + granteeAl := dynamicaccess.NewLogic(granteeDH) + granteeCtrl := dynamicaccess.NewController(granteeAl) + // download with grantee shall still work with the timestamp before the revoke + decRef, err := granteeCtrl.DownloadHandler(ctx, ls, encRef, &publisher.PublicKey, hrefUpdate2, beforeRevokeTS) + require.NoError(t, err) + assert.Equal(t, ref, decRef) + + // download with grantee shall NOT work with the latest timestamp + decRef, err = granteeCtrl.DownloadHandler(ctx, ls, encRef, &publisher.PublicKey, hrefUpdate2, time.Now().Unix()) + require.Error(t, err) + assert.Equal(t, swarm.ZeroAddress, decRef) + + // publisher shall still be able to download with the timestamp before the revoke + decRef, err = c.DownloadHandler(ctx, ls, encRef, &publisher.PublicKey, hrefUpdate2, beforeRevokeTS) + require.NoError(t, err) + assert.Equal(t, ref, decRef) + }) t.Run("add twice", func(t *testing.T) { addList := []*ecdsa.PublicKey{&grantee.PublicKey, &grantee.PublicKey} //nolint:ineffassign,staticcheck,wastedassign granteeRef, eglref, _, _, err := c.UpdateHandler(ctx, ls, gls, swarm.ZeroAddress, href, &publisher.PublicKey, addList, nil) granteeRef, _, _, _, _ = c.UpdateHandler(ctx, ls, ls, eglref, href, &publisher.PublicKey, addList, nil) - gl, err := dynamicaccess.NewGranteeListReference(ctx, createLs(), granteeRef) + gl, err := dynamicaccess.NewGranteeListReference(ctx, ls, granteeRef) assert.NoError(t, err) assert.Len(t, gl.Get(), 1) @@ -220,7 +260,7 @@ func TestController_UpdateHandler(t *testing.T) { t.Run("revoke non-existing", func(t *testing.T) { addList := []*ecdsa.PublicKey{&grantee.PublicKey} granteeRef, _, _, _, _ := c.UpdateHandler(ctx, ls, ls, swarm.ZeroAddress, href, &publisher.PublicKey, addList, nil) - gl, err := dynamicaccess.NewGranteeListReference(ctx, createLs(), granteeRef) + gl, err := dynamicaccess.NewGranteeListReference(ctx, ls, granteeRef) assert.NoError(t, err) assert.Len(t, gl.Get(), 1) diff --git a/pkg/dynamicaccess/mock/controller.go b/pkg/dynamicaccess/mock/controller.go index 3db86c88020..30f617bfccb 100644 --- a/pkg/dynamicaccess/mock/controller.go +++ b/pkg/dynamicaccess/mock/controller.go @@ -25,7 +25,7 @@ import ( "golang.org/x/crypto/sha3" ) -type mockDacService struct { +type mockController struct { historyMap map[string]dynamicaccess.History refMap map[string]swarm.Address acceptAll bool @@ -34,19 +34,19 @@ type mockDacService struct { ls file.LoadSaver } -type optionFunc func(*mockDacService) +type optionFunc func(*mockController) // Option is an option passed to a mock dynamicaccess Service. type Option interface { - apply(*mockDacService) + apply(*mockController) } -func (f optionFunc) apply(r *mockDacService) { f(r) } +func (f optionFunc) apply(r *mockController) { f(r) } // New creates a new mock dynamicaccess service. func New(o ...Option) dynamicaccess.Controller { storer := mockstorer.New() - m := &mockDacService{ + m := &mockController{ historyMap: make(map[string]dynamicaccess.History), refMap: make(map[string]swarm.Address), publisher: "", @@ -62,23 +62,23 @@ func New(o ...Option) dynamicaccess.Controller { // WithAcceptAll sets the mock to return fixed references on every call to DownloadHandler. func WithAcceptAll() Option { - return optionFunc(func(m *mockDacService) { m.acceptAll = true }) + return optionFunc(func(m *mockController) { m.acceptAll = true }) } func WithHistory(h dynamicaccess.History, ref string) Option { - return optionFunc(func(m *mockDacService) { + return optionFunc(func(m *mockController) { m.historyMap = map[string]dynamicaccess.History{ref: h} }) } func WithPublisher(ref string) Option { - return optionFunc(func(m *mockDacService) { + return optionFunc(func(m *mockController) { m.publisher = ref m.encrypter = encryption.New(encryption.Key(ref), 0, uint32(0), sha3.NewLegacyKeccak256) }) } -func (m *mockDacService) DownloadHandler(ctx context.Context, ls file.LoadSaver, encryptedRef swarm.Address, publisher *ecdsa.PublicKey, historyRootHash swarm.Address, timestamp int64) (swarm.Address, error) { +func (m *mockController) DownloadHandler(ctx context.Context, ls file.LoadSaver, encryptedRef swarm.Address, publisher *ecdsa.PublicKey, historyRootHash swarm.Address, timestamp int64) (swarm.Address, error) { if m.acceptAll { return swarm.ParseHexAddress("36e6c1bbdfee6ac21485d5f970479fd1df458d36df9ef4e8179708ed46da557f") } @@ -101,7 +101,7 @@ func (m *mockDacService) DownloadHandler(ctx context.Context, ls file.LoadSaver, return m.refMap[encryptedRef.String()], nil } -func (m *mockDacService) UploadHandler(ctx context.Context, ls file.LoadSaver, reference swarm.Address, publisher *ecdsa.PublicKey, historyRootHash swarm.Address) (swarm.Address, swarm.Address, swarm.Address, error) { +func (m *mockController) UploadHandler(ctx context.Context, ls file.LoadSaver, reference swarm.Address, publisher *ecdsa.PublicKey, historyRootHash swarm.Address) (swarm.Address, swarm.Address, swarm.Address, error) { historyRef, _ := swarm.ParseHexAddress("67bdf80a9bbea8eca9c8480e43fdceb485d2d74d5708e45144b8c4adacd13d9c") kvsRef, _ := swarm.ParseHexAddress("3339613565613837623134316665343461613630396333333237656364383934") if m.acceptAll { @@ -136,11 +136,11 @@ func (m *mockDacService) UploadHandler(ctx context.Context, ls file.LoadSaver, r return kvsRef, historyRef, swarm.NewAddress(encryptedRef), nil } -func (m *mockDacService) Close() error { +func (m *mockController) Close() error { return nil } -func (m *mockDacService) UpdateHandler(_ context.Context, ls file.LoadSaver, gls file.LoadSaver, encryptedglref swarm.Address, historyref swarm.Address, publisher *ecdsa.PublicKey, addList []*ecdsa.PublicKey, removeList []*ecdsa.PublicKey) (swarm.Address, swarm.Address, swarm.Address, swarm.Address, error) { +func (m *mockController) UpdateHandler(_ context.Context, ls file.LoadSaver, gls file.LoadSaver, encryptedglref swarm.Address, historyref swarm.Address, publisher *ecdsa.PublicKey, addList []*ecdsa.PublicKey, removeList []*ecdsa.PublicKey) (swarm.Address, swarm.Address, swarm.Address, swarm.Address, error) { historyRef, _ := swarm.ParseHexAddress("67bdf80a9bbea8eca9c8480e43fdceb485d2d74d5708e45144b8c4adacd13d9c") glRef, _ := swarm.ParseHexAddress("3339613565613837623134316665343461613630396333333237656364383934") eglRef, _ := swarm.ParseHexAddress("fc4e9fe978991257b897d987bc4ff13058b66ef45a53189a0b4fe84bb3346396") @@ -148,7 +148,7 @@ func (m *mockDacService) UpdateHandler(_ context.Context, ls file.LoadSaver, gls return glRef, eglRef, historyRef, actref, nil } -func (m *mockDacService) Get(ctx context.Context, ls file.LoadSaver, publisher *ecdsa.PublicKey, encryptedglref swarm.Address) ([]*ecdsa.PublicKey, error) { +func (m *mockController) Get(ctx context.Context, ls file.LoadSaver, publisher *ecdsa.PublicKey, encryptedglref swarm.Address) ([]*ecdsa.PublicKey, error) { if m.publisher == "" { return nil, fmt.Errorf("granteelist not found") } @@ -180,4 +180,4 @@ func requestPipelineFactory(ctx context.Context, s storage.Putter, encrypt bool, } } -var _ dynamicaccess.Controller = (*mockDacService)(nil) +var _ dynamicaccess.Controller = (*mockController)(nil) diff --git a/pkg/kvs/kvs.go b/pkg/kvs/kvs.go index 5f2381a8b24..0c7eae48c08 100644 --- a/pkg/kvs/kvs.go +++ b/pkg/kvs/kvs.go @@ -58,23 +58,23 @@ func (s *keyValueStore) Save(ctx context.Context) (swarm.Address, error) { } func New(ls file.LoadSaver) (KeyValueStore, error) { - manif, err := manifest.NewSimpleManifest(ls) + m, err := manifest.NewSimpleManifest(ls) if err != nil { return nil, err } return &keyValueStore{ - manifest: manif, + manifest: m, }, nil } -func NewReference(ls file.LoadSaver, rootHash swarm.Address) (KeyValueStore, error) { - manif, err := manifest.NewSimpleManifestReference(rootHash, ls) +func NewReference(ls file.LoadSaver, ref swarm.Address) (KeyValueStore, error) { + m, err := manifest.NewSimpleManifestReference(ref, ls) if err != nil { return nil, err } return &keyValueStore{ - manifest: manif, + manifest: m, }, nil } From d1ea594d127504e50436d8e4c0a26ddf3ec58dbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A1lint=20Ujv=C3=A1ri?= <58116288+bosi95@users.noreply.github.com> Date: Thu, 6 Jun 2024 16:38:56 +0200 Subject: [PATCH 34/34] refactor(act): fix pr comments (#50) * refactor(act): fix pr comments * refactor(act): fix linter errors * refactor(act): move kvs to accesscontrol as a subpackage * refactor(act): typo and comment fix --- cmd/bee/cmd/start.go | 8 +- .../access.go} | 63 +++--- .../access_test.go} | 28 +-- .../controller.go | 49 +++-- .../controller_test.go | 86 ++++---- .../grantee.go | 31 ++- .../grantee_test.go | 32 +-- .../history.go | 44 ++-- .../history_test.go | 14 +- pkg/{ => accesscontrol}/kvs/kvs.go | 31 ++- pkg/{ => accesscontrol}/kvs/kvs_test.go | 10 +- pkg/{ => accesscontrol}/kvs/mock/kvs.go | 3 +- .../mock/controller.go | 28 ++- .../mock/grantee.go | 0 .../mock/session.go | 0 .../session.go | 27 ++- .../session_test.go | 21 +- .../{dynamicaccess.go => accesscontrol.go} | 51 +++-- ...icaccess_test.go => accesscontrol_test.go} | 205 +++++++++--------- pkg/api/api.go | 8 +- pkg/api/api_test.go | 12 +- pkg/api/bzz.go | 12 +- pkg/api/chunk.go | 6 +- pkg/api/soc.go | 6 +- pkg/dynamicaccess/mock/accesslogic.go | 15 -- pkg/node/devnode.go | 32 +-- pkg/node/node.go | 16 +- pkg/soc/testing/soc.go | 2 +- 28 files changed, 446 insertions(+), 394 deletions(-) rename pkg/{dynamicaccess/accesslogic.go => accesscontrol/access.go} (75%) rename pkg/{dynamicaccess/accesslogic_test.go => accesscontrol/access_test.go} (89%) rename pkg/{dynamicaccess => accesscontrol}/controller.go (82%) rename pkg/{dynamicaccess => accesscontrol}/controller_test.go (81%) rename pkg/{dynamicaccess => accesscontrol}/grantee.go (79%) rename pkg/{dynamicaccess => accesscontrol}/grantee_test.go (87%) rename pkg/{dynamicaccess => accesscontrol}/history.go (74%) rename pkg/{dynamicaccess => accesscontrol}/history_test.go (94%) rename pkg/{ => accesscontrol}/kvs/kvs.go (58%) rename pkg/{ => accesscontrol}/kvs/kvs_test.go (96%) rename pkg/{ => accesscontrol}/kvs/mock/kvs.go (93%) rename pkg/{dynamicaccess => accesscontrol}/mock/controller.go (88%) rename pkg/{dynamicaccess => accesscontrol}/mock/grantee.go (100%) rename pkg/{dynamicaccess => accesscontrol}/mock/session.go (100%) rename pkg/{dynamicaccess => accesscontrol}/session.go (69%) rename pkg/{dynamicaccess => accesscontrol}/session_test.go (84%) rename pkg/api/{dynamicaccess.go => accesscontrol.go} (87%) rename pkg/api/{dynamicaccess_test.go => accesscontrol_test.go} (89%) delete mode 100644 pkg/dynamicaccess/mock/accesslogic.go diff --git a/cmd/bee/cmd/start.go b/cmd/bee/cmd/start.go index 9403dc69571..c0378e52e2b 100644 --- a/cmd/bee/cmd/start.go +++ b/cmd/bee/cmd/start.go @@ -24,10 +24,10 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/rpc" "github.com/ethersphere/bee/v2" + "github.com/ethersphere/bee/v2/pkg/accesscontrol" chaincfg "github.com/ethersphere/bee/v2/pkg/config" "github.com/ethersphere/bee/v2/pkg/crypto" "github.com/ethersphere/bee/v2/pkg/crypto/clef" - "github.com/ethersphere/bee/v2/pkg/dynamicaccess" "github.com/ethersphere/bee/v2/pkg/keystore" filekeystore "github.com/ethersphere/bee/v2/pkg/keystore/file" memkeystore "github.com/ethersphere/bee/v2/pkg/keystore/mem" @@ -374,7 +374,7 @@ type signerConfig struct { publicKey *ecdsa.PublicKey libp2pPrivateKey *ecdsa.PrivateKey pssPrivateKey *ecdsa.PrivateKey - session dynamicaccess.Session + session accesscontrol.Session } func waitForClef(logger log.Logger, maxRetries uint64, endpoint string) (externalSigner *external.ExternalSigner, err error) { @@ -405,7 +405,7 @@ func (c *command) configureSigner(cmd *cobra.Command, logger log.Logger) (config var signer crypto.Signer var password string var publicKey *ecdsa.PublicKey - var session dynamicaccess.Session + var session accesscontrol.Session if p := c.config.GetString(optionNamePassword); p != "" { password = p } else if pf := c.config.GetString(optionNamePasswordFile); pf != "" { @@ -478,7 +478,7 @@ func (c *command) configureSigner(cmd *cobra.Command, logger log.Logger) (config } signer = crypto.NewDefaultSigner(swarmPrivateKey) publicKey = &swarmPrivateKey.PublicKey - session = dynamicaccess.NewDefaultSession(swarmPrivateKey) + session = accesscontrol.NewDefaultSession(swarmPrivateKey) } logger.Info("swarm public key", "public_key", hex.EncodeToString(crypto.EncodeSecp256k1PublicKey(publicKey))) diff --git a/pkg/dynamicaccess/accesslogic.go b/pkg/accesscontrol/access.go similarity index 75% rename from pkg/dynamicaccess/accesslogic.go rename to pkg/accesscontrol/access.go index 671537486e9..79f60facb2d 100644 --- a/pkg/dynamicaccess/accesslogic.go +++ b/pkg/accesscontrol/access.go @@ -2,15 +2,15 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package dynamicaccess +package accesscontrol import ( "context" "crypto/ecdsa" "fmt" + "github.com/ethersphere/bee/v2/pkg/accesscontrol/kvs" "github.com/ethersphere/bee/v2/pkg/encryption" - "github.com/ethersphere/bee/v2/pkg/kvs" "github.com/ethersphere/bee/v2/pkg/swarm" "golang.org/x/crypto/sha3" ) @@ -24,7 +24,7 @@ var ( // Decryptor is a read-only interface for the ACT. type Decryptor interface { - // DecryptRef will return a decrypted reference, for given encrypted reference and grantee + // DecryptRef will return a decrypted reference, for given encrypted reference and grantee. DecryptRef(ctx context.Context, storage kvs.KeyValueStore, encryptedRef swarm.Address, publisher *ecdsa.PublicKey) (swarm.Address, error) Session } @@ -32,19 +32,20 @@ type Decryptor interface { // Control interface for the ACT (does write operations). type Control interface { Decryptor - // AddGrantee adds a new grantee to the ACT + // AddGrantee adds a new grantee to the ACT. AddGrantee(ctx context.Context, storage kvs.KeyValueStore, publisherPubKey, granteePubKey *ecdsa.PublicKey) error - // EncryptRef encrypts a Swarm reference for a given grantee + // EncryptRef encrypts a Swarm reference for a given grantee. EncryptRef(ctx context.Context, storage kvs.KeyValueStore, grantee *ecdsa.PublicKey, ref swarm.Address) (swarm.Address, error) } +// ActLogic represents the access control logic. type ActLogic struct { Session } var _ Control = (*ActLogic)(nil) -// EncryptRef encrypts a SWARM reference for a publisher. +// EncryptRef encrypts a Swarm reference for a publisher. func (al ActLogic) EncryptRef(ctx context.Context, storage kvs.KeyValueStore, publisherPubKey *ecdsa.PublicKey, ref swarm.Address) (swarm.Address, error) { accessKey, err := al.getAccessKey(ctx, storage, publisherPubKey) if err != nil { @@ -66,32 +67,36 @@ func (al ActLogic) AddGrantee(ctx context.Context, storage kvs.KeyValueStore, pu err error ) - // Create new access key because grantee is the publisher + // Create new access key because grantee is the publisher. if publisherPubKey.Equal(granteePubKey) { accessKey = encryption.GenerateRandomKey(encryption.KeyLength) } else { - // Get previously generated access key + // Get previously generated access key. accessKey, err = al.getAccessKey(ctx, storage, publisherPubKey) if err != nil { return err } } - // Encrypt the access key for the new Grantee lookupKey, accessKeyDecryptionKey, err := al.getKeys(granteePubKey) if err != nil { return err } - // Encrypt the access key for the new Grantee + // Encrypt the access key for the new Grantee. cipher := encryption.New(encryption.Key(accessKeyDecryptionKey), 0, uint32(0), hashFunc) granteeEncryptedAccessKey, err := cipher.Encrypt(accessKey) if err != nil { return fmt.Errorf("failed to encrypt access key: %w", err) } - // Add the new encrypted access key to the Act - return storage.Put(ctx, lookupKey, granteeEncryptedAccessKey) + // Add the new encrypted access key to the Act. + err = storage.Put(ctx, lookupKey, granteeEncryptedAccessKey) + if err != nil { + return fmt.Errorf("failed to put value to KVS: %w", err) + } + + return nil } // Will return the access key for a publisher (public key). @@ -100,56 +105,48 @@ func (al *ActLogic) getAccessKey(ctx context.Context, storage kvs.KeyValueStore, if err != nil { return nil, err } - // no need for constructor call if value not found in act + // no need for constructor call if value not found in act. accessKeyDecryptionCipher := encryption.New(encryption.Key(publisherAKDecryptionKey), 0, uint32(0), hashFunc) encryptedAK, err := storage.Get(ctx, publisherLookupKey) if err != nil { return nil, fmt.Errorf("failed go get value from KVS: %w", err) } - return accessKeyDecryptionCipher.Decrypt(encryptedAK) + accessKey, err := accessKeyDecryptionCipher.Decrypt(encryptedAK) + if err != nil { + return nil, fmt.Errorf("failed to decrypt access key: %w", err) + } + + return accessKey, nil } -// Generate lookup key and access key decryption key for a given public key +// Generate lookup key and access key decryption key for a given public key. func (al *ActLogic) getKeys(publicKey *ecdsa.PublicKey) ([]byte, []byte, error) { nonces := [][]byte{zeroByteArray, oneByteArray} keys, err := al.Session.Key(publicKey, nonces) - if keys == nil { + if len(keys) != len(nonces) { return nil, nil, err } return keys[0], keys[1], err } -// DecryptRef will return a decrypted reference, for given encrypted reference and publisher +// DecryptRef will return a decrypted reference, for given encrypted reference and publisher. func (al ActLogic) DecryptRef(ctx context.Context, storage kvs.KeyValueStore, encryptedRef swarm.Address, publisher *ecdsa.PublicKey) (swarm.Address, error) { - lookupKey, accessKeyDecryptionKey, err := al.getKeys(publisher) - if err != nil { - return swarm.ZeroAddress, err - } - - // Lookup encrypted access key from the ACT manifest - encryptedAccessKey, err := storage.Get(ctx, lookupKey) - if err != nil { - return swarm.ZeroAddress, fmt.Errorf("failed to get access key from KVS: %w", err) - } - - // Decrypt access key - accessKeyCipher := encryption.New(encryption.Key(accessKeyDecryptionKey), 0, uint32(0), hashFunc) - accessKey, err := accessKeyCipher.Decrypt(encryptedAccessKey) + accessKey, err := al.getAccessKey(ctx, storage, publisher) if err != nil { return swarm.ZeroAddress, err } - // Decrypt reference refCipher := encryption.New(accessKey, 0, uint32(0), hashFunc) ref, err := refCipher.Decrypt(encryptedRef.Bytes()) if err != nil { - return swarm.ZeroAddress, err + return swarm.ZeroAddress, fmt.Errorf("failed to decrypt reference: %w", err) } return swarm.NewAddress(ref), nil } +// NewLogic creates a new ACT Logic from a session. func NewLogic(s Session) ActLogic { return ActLogic{ Session: s, diff --git a/pkg/dynamicaccess/accesslogic_test.go b/pkg/accesscontrol/access_test.go similarity index 89% rename from pkg/dynamicaccess/accesslogic_test.go rename to pkg/accesscontrol/access_test.go index b8cab2f8fbf..d976131ec92 100644 --- a/pkg/dynamicaccess/accesslogic_test.go +++ b/pkg/accesscontrol/access_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package dynamicaccess_test +package accesscontrol_test import ( "context" @@ -12,18 +12,18 @@ import ( "encoding/hex" "testing" + "github.com/ethersphere/bee/v2/pkg/accesscontrol" + kvsmock "github.com/ethersphere/bee/v2/pkg/accesscontrol/kvs/mock" "github.com/ethersphere/bee/v2/pkg/crypto" - "github.com/ethersphere/bee/v2/pkg/dynamicaccess" - kvsmock "github.com/ethersphere/bee/v2/pkg/kvs/mock" "github.com/ethersphere/bee/v2/pkg/swarm" "github.com/stretchr/testify/assert" ) -// Generates a new test environment with a fix private key -func setupAccessLogic() dynamicaccess.ActLogic { +// Generates a new test environment with a fix private key. +func setupAccessLogic() accesscontrol.ActLogic { privateKey := getPrivKey(1) - diffieHellman := dynamicaccess.NewDefaultSession(privateKey) - al := dynamicaccess.NewLogic(diffieHellman) + diffieHellman := accesscontrol.NewDefaultSession(privateKey) + al := accesscontrol.NewLogic(diffieHellman) return al } @@ -87,8 +87,8 @@ func TestDecryptRefWithGrantee_Success(t *testing.T) { t.Parallel() ctx := context.Background() id0, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - diffieHellman := dynamicaccess.NewDefaultSession(id0) - al := dynamicaccess.NewLogic(diffieHellman) + diffieHellman := accesscontrol.NewDefaultSession(id0) + al := accesscontrol.NewLogic(diffieHellman) s := kvsmock.New() err := al.AddGrantee(ctx, s, &id0.PublicKey, &id0.PublicKey) @@ -111,8 +111,8 @@ func TestDecryptRefWithGrantee_Success(t *testing.T) { t.Fatalf("There was an error while calling EncryptRef: %v", err) } - diffieHellman2 := dynamicaccess.NewDefaultSession(id1) - granteeAccessLogic := dynamicaccess.NewLogic(diffieHellman2) + diffieHellman2 := accesscontrol.NewDefaultSession(id1) + granteeAccessLogic := accesscontrol.NewLogic(diffieHellman2) actualRef, err := granteeAccessLogic.DecryptRef(ctx, s, encryptedRef, &id0.PublicKey) if err != nil { t.Fatalf("There was an error while calling Get: %v", err) @@ -139,8 +139,7 @@ func TestDecryptRef_Error(t *testing.T) { r, err := al.DecryptRef(ctx, s, encryptedRef, nil) if err == nil { - t.Logf("r: %s", r.String()) - t.Fatalf("Get should return encrypted access key not found error!") + t.Fatalf("Get should return error but got reference: %v", r) } } @@ -168,9 +167,6 @@ func TestAddPublisher(t *testing.T) { if len(decodedEncryptedAccessKey) != 64 { t.Fatalf("AddGrantee: expected encrypted access key length 64, got %d", len(decodedEncryptedAccessKey)) } - if s == nil { - t.Fatalf("AddGrantee: expected act, got nil") - } } func TestAddNewGranteeToContent(t *testing.T) { diff --git a/pkg/dynamicaccess/controller.go b/pkg/accesscontrol/controller.go similarity index 82% rename from pkg/dynamicaccess/controller.go rename to pkg/accesscontrol/controller.go index dffe4bbdf64..f2c843c6b36 100644 --- a/pkg/dynamicaccess/controller.go +++ b/pkg/accesscontrol/controller.go @@ -2,20 +2,24 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package dynamicaccess +// Package accesscontrol provides functionalities needed +// for managing access control on Swarm +package accesscontrol import ( "context" "crypto/ecdsa" + "fmt" "io" "time" + "github.com/ethersphere/bee/v2/pkg/accesscontrol/kvs" "github.com/ethersphere/bee/v2/pkg/encryption" "github.com/ethersphere/bee/v2/pkg/file" - "github.com/ethersphere/bee/v2/pkg/kvs" "github.com/ethersphere/bee/v2/pkg/swarm" ) +// Grantees represents an interface for managing and retrieving grantees for a publisher. type Grantees interface { // UpdateHandler manages the grantees for the given publisher, updating the list based on provided public keys to add or remove. // Only the publisher can make changes to the grantee list. @@ -25,6 +29,8 @@ type Grantees interface { Get(ctx context.Context, ls file.LoadSaver, publisher *ecdsa.PublicKey, encryptedglRef swarm.Address) ([]*ecdsa.PublicKey, error) } +// Controller the interface for managing access control on Swarm. +// It provides methods for handling downloads, uploads and updates for grantee lists and references. type Controller interface { Grantees // DownloadHandler decrypts the encryptedRef using the lookupkey based on the history and timestamp. @@ -34,18 +40,21 @@ type Controller interface { io.Closer } +// ControllerStruct represents a controller for access control logic. type ControllerStruct struct { - accessLogic ActLogic + access ActLogic } var _ Controller = (*ControllerStruct)(nil) -func NewController(accessLogic ActLogic) *ControllerStruct { +// NewController creates a new access controller with the given access logic. +func NewController(access ActLogic) *ControllerStruct { return &ControllerStruct{ - accessLogic: accessLogic, + access: access, } } +// DownloadHandler decrypts the encryptedRef using the lookupkey based on the history and timestamp. func (c *ControllerStruct) DownloadHandler( ctx context.Context, ls file.LoadSaver, @@ -59,9 +68,10 @@ func (c *ControllerStruct) DownloadHandler( return swarm.ZeroAddress, err } - return c.accessLogic.DecryptRef(ctx, act, encryptedRef, publisher) + return c.access.DecryptRef(ctx, act, encryptedRef, publisher) } +// UploadHandler encrypts the reference and stores it in the history as the latest update. func (c *ControllerStruct) UploadHandler( ctx context.Context, ls file.LoadSaver, @@ -82,12 +92,14 @@ func (c *ControllerStruct) UploadHandler( } } - encryptedRef, err := c.accessLogic.EncryptRef(ctx, act, publisher, reference) + encryptedRef, err := c.access.EncryptRef(ctx, act, publisher, reference) return actRef, newHistoryRef, encryptedRef, err } +// UpdateHandler manages the grantees for the given publisher, updating the list based on provided public keys to add or remove. +// Only the publisher can make changes to the grantee list. // Limitation: If an upadate is called again within a second from the latest upload/update then mantaray save fails with ErrInvalidInput, -// because the key (timestamp) is already present, hence a new fork is not created +// because the key (timestamp) is already present, hence a new fork is not created. func (c *ControllerStruct) UpdateHandler( ctx context.Context, ls file.LoadSaver, @@ -130,7 +142,7 @@ func (c *ControllerStruct) UpdateHandler( } for _, grantee := range granteesToAdd { - err := c.accessLogic.AddGrantee(ctx, act, publisher, grantee) + err := c.access.AddGrantee(ctx, act, publisher, grantee) if err != nil { return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, err } @@ -162,6 +174,8 @@ func (c *ControllerStruct) UpdateHandler( return granteeRef, egranteeRef, hRef, actRef, nil } +// Get returns the list of grantees for the given publisher. +// The list is accessible only by the publisher. func (c *ControllerStruct) Get(ctx context.Context, ls file.LoadSaver, publisher *ecdsa.PublicKey, encryptedglRef swarm.Address) ([]*ecdsa.PublicKey, error) { gl, err := c.getGranteeList(ctx, ls, encryptedglRef, publisher) if err != nil { @@ -175,7 +189,7 @@ func (c *ControllerStruct) newActWithPublisher(ctx context.Context, ls file.Load if err != nil { return nil, err } - err = c.accessLogic.AddGrantee(ctx, act, publisher, publisher) + err = c.access.AddGrantee(ctx, act, publisher, publisher) if err != nil { return nil, err } @@ -230,10 +244,7 @@ func (c *ControllerStruct) saveHistoryAndAct(ctx context.Context, history Histor func (c *ControllerStruct) getGranteeList(ctx context.Context, ls file.LoadSaver, encryptedglRef swarm.Address, publisher *ecdsa.PublicKey) (gl GranteeList, err error) { if encryptedglRef.IsZero() { - gl, err = NewGranteeList(ls) - if err != nil { - return nil, err - } + gl = NewGranteeList(ls) } else { granteeref, err := c.decryptRefForPublisher(publisher, encryptedglRef) if err != nil { @@ -250,34 +261,34 @@ func (c *ControllerStruct) getGranteeList(ctx context.Context, ls file.LoadSaver } func (c *ControllerStruct) encryptRefForPublisher(publisherPubKey *ecdsa.PublicKey, ref swarm.Address) (swarm.Address, error) { - keys, err := c.accessLogic.Session.Key(publisherPubKey, [][]byte{oneByteArray}) + keys, err := c.access.Session.Key(publisherPubKey, [][]byte{oneByteArray}) if err != nil { return swarm.ZeroAddress, err } refCipher := encryption.New(keys[0], 0, uint32(0), hashFunc) encryptedRef, err := refCipher.Encrypt(ref.Bytes()) if err != nil { - return swarm.ZeroAddress, err + return swarm.ZeroAddress, fmt.Errorf("failed to encrypt reference: %w", err) } return swarm.NewAddress(encryptedRef), nil } func (c *ControllerStruct) decryptRefForPublisher(publisherPubKey *ecdsa.PublicKey, encryptedRef swarm.Address) (swarm.Address, error) { - keys, err := c.accessLogic.Session.Key(publisherPubKey, [][]byte{oneByteArray}) + keys, err := c.access.Session.Key(publisherPubKey, [][]byte{oneByteArray}) if err != nil { return swarm.ZeroAddress, err } refCipher := encryption.New(keys[0], 0, uint32(0), hashFunc) ref, err := refCipher.Decrypt(encryptedRef.Bytes()) if err != nil { - return swarm.ZeroAddress, err + return swarm.ZeroAddress, fmt.Errorf("failed to decrypt reference: %w", err) } return swarm.NewAddress(ref), nil } -// TODO: what to do in close ? +// Close simply returns nil func (c *ControllerStruct) Close() error { return nil } diff --git a/pkg/dynamicaccess/controller_test.go b/pkg/accesscontrol/controller_test.go similarity index 81% rename from pkg/dynamicaccess/controller_test.go rename to pkg/accesscontrol/controller_test.go index c25d8079bfa..11a922cacae 100644 --- a/pkg/dynamicaccess/controller_test.go +++ b/pkg/accesscontrol/controller_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package dynamicaccess_test +package accesscontrol_test import ( "context" @@ -11,12 +11,12 @@ import ( "testing" "time" - "github.com/ethersphere/bee/v2/pkg/dynamicaccess" + "github.com/ethersphere/bee/v2/pkg/accesscontrol" + "github.com/ethersphere/bee/v2/pkg/accesscontrol/kvs" encryption "github.com/ethersphere/bee/v2/pkg/encryption" "github.com/ethersphere/bee/v2/pkg/file" "github.com/ethersphere/bee/v2/pkg/file/loadsave" "github.com/ethersphere/bee/v2/pkg/file/redundancy" - "github.com/ethersphere/bee/v2/pkg/kvs" "github.com/ethersphere/bee/v2/pkg/swarm" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -24,8 +24,8 @@ import ( ) //nolint:errcheck,gosec,wrapcheck -func getHistoryFixture(ctx context.Context, ls file.LoadSaver, al dynamicaccess.ActLogic, publisher *ecdsa.PublicKey) (swarm.Address, error) { - h, err := dynamicaccess.NewHistory(ls) +func getHistoryFixture(ctx context.Context, ls file.LoadSaver, al accesscontrol.ActLogic, publisher *ecdsa.PublicKey) (swarm.Address, error) { + h, err := accesscontrol.NewHistory(ls) if err != nil { return swarm.ZeroAddress, err } @@ -57,9 +57,9 @@ func TestController_UploadHandler(t *testing.T) { t.Parallel() ctx := context.Background() publisher := getPrivKey(0) - diffieHellman := dynamicaccess.NewDefaultSession(publisher) - al := dynamicaccess.NewLogic(diffieHellman) - c := dynamicaccess.NewController(al) + diffieHellman := accesscontrol.NewDefaultSession(publisher) + al := accesscontrol.NewLogic(diffieHellman) + c := accesscontrol.NewController(al) ls := createLs() t.Run("New upload", func(t *testing.T) { @@ -67,7 +67,7 @@ func TestController_UploadHandler(t *testing.T) { _, hRef, encRef, err := c.UploadHandler(ctx, ls, ref, &publisher.PublicKey, swarm.ZeroAddress) assert.NoError(t, err) - h, _ := dynamicaccess.NewHistoryReference(ls, hRef) + h, _ := accesscontrol.NewHistoryReference(ls, hRef) entry, _ := h.Lookup(ctx, time.Now().Unix()) actRef := entry.Reference() act, _ := kvs.NewReference(ls, actRef) @@ -84,20 +84,20 @@ func TestController_UploadHandler(t *testing.T) { assert.NoError(t, err) _, hRef2, encRef, err := c.UploadHandler(ctx, ls, ref, &publisher.PublicKey, hRef1) assert.NoError(t, err) - h, err := dynamicaccess.NewHistoryReference(ls, hRef2) + h, err := accesscontrol.NewHistoryReference(ls, hRef2) assert.NoError(t, err) hRef2, err = h.Store(ctx) assert.NoError(t, err) assert.True(t, hRef1.Equal(hRef2)) - h, _ = dynamicaccess.NewHistoryReference(ls, hRef2) + h, _ = accesscontrol.NewHistoryReference(ls, hRef2) entry, _ := h.Lookup(ctx, time.Now().Unix()) actRef := entry.Reference() act, _ := kvs.NewReference(ls, actRef) expRef, err := al.EncryptRef(ctx, act, &publisher.PublicKey, ref) assert.NoError(t, err) - assert.Equal(t, encRef, expRef) + assert.Equal(t, expRef, encRef) assert.NotEqual(t, hRef2, swarm.ZeroAddress) }) } @@ -106,13 +106,13 @@ func TestController_PublisherDownload(t *testing.T) { t.Parallel() ctx := context.Background() publisher := getPrivKey(0) - diffieHellman := dynamicaccess.NewDefaultSession(publisher) - al := dynamicaccess.NewLogic(diffieHellman) - c := dynamicaccess.NewController(al) + diffieHellman := accesscontrol.NewDefaultSession(publisher) + al := accesscontrol.NewLogic(diffieHellman) + c := accesscontrol.NewController(al) ls := createLs() ref := swarm.RandAddress(t) href, _ := getHistoryFixture(ctx, ls, al, &publisher.PublicKey) - h, _ := dynamicaccess.NewHistoryReference(ls, href) + h, _ := accesscontrol.NewHistoryReference(ls, href) entry, _ := h.Lookup(ctx, time.Now().Unix()) actRef := entry.Reference() act, _ := kvs.NewReference(ls, actRef) @@ -129,16 +129,16 @@ func TestController_GranteeDownload(t *testing.T) { ctx := context.Background() publisher := getPrivKey(0) grantee := getPrivKey(2) - publisherDH := dynamicaccess.NewDefaultSession(publisher) - publisherAL := dynamicaccess.NewLogic(publisherDH) + publisherDH := accesscontrol.NewDefaultSession(publisher) + publisherAL := accesscontrol.NewLogic(publisherDH) - diffieHellman := dynamicaccess.NewDefaultSession(grantee) - al := dynamicaccess.NewLogic(diffieHellman) + diffieHellman := accesscontrol.NewDefaultSession(grantee) + al := accesscontrol.NewLogic(diffieHellman) ls := createLs() - c := dynamicaccess.NewController(al) + c := accesscontrol.NewController(al) ref := swarm.RandAddress(t) href, _ := getHistoryFixture(ctx, ls, publisherAL, &publisher.PublicKey) - h, _ := dynamicaccess.NewHistoryReference(ls, href) + h, _ := accesscontrol.NewHistoryReference(ls, href) ts := time.Date(2001, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() entry, _ := h.Lookup(ctx, ts) actRef := entry.Reference() @@ -155,13 +155,13 @@ func TestController_UpdateHandler(t *testing.T) { t.Parallel() ctx := context.Background() publisher := getPrivKey(1) - diffieHellman := dynamicaccess.NewDefaultSession(publisher) - al := dynamicaccess.NewLogic(diffieHellman) + diffieHellman := accesscontrol.NewDefaultSession(publisher) + al := accesscontrol.NewLogic(diffieHellman) keys, _ := al.Session.Key(&publisher.PublicKey, [][]byte{{1}}) refCipher := encryption.New(keys[0], 0, uint32(0), sha3.NewLegacyKeccak256) ls := createLs() gls := loadsave.New(mockStorer.ChunkStore(), mockStorer.Cache(), requestPipelineFactory(context.Background(), mockStorer.Cache(), true, redundancy.NONE)) - c := dynamicaccess.NewController(al) + c := accesscontrol.NewController(al) href, _ := getHistoryFixture(ctx, ls, al, &publisher.PublicKey) grantee1 := getPrivKey(0) @@ -172,7 +172,7 @@ func TestController_UpdateHandler(t *testing.T) { granteeRef, _, _, _, err := c.UpdateHandler(ctx, ls, ls, swarm.ZeroAddress, swarm.ZeroAddress, &publisher.PublicKey, addList, nil) assert.NoError(t, err) - gl, err := dynamicaccess.NewGranteeListReference(ctx, ls, granteeRef) + gl, err := accesscontrol.NewGranteeListReference(ctx, ls, granteeRef) assert.NoError(t, err) assert.Len(t, gl.Get(), 1) @@ -182,27 +182,27 @@ func TestController_UpdateHandler(t *testing.T) { granteeRef, eglref, _, _, err := c.UpdateHandler(ctx, ls, gls, swarm.ZeroAddress, href, &publisher.PublicKey, addList, nil) assert.NoError(t, err) - gl, err := dynamicaccess.NewGranteeListReference(ctx, ls, granteeRef) + gl, err := accesscontrol.NewGranteeListReference(ctx, ls, granteeRef) assert.NoError(t, err) assert.Len(t, gl.Get(), 1) addList = []*ecdsa.PublicKey{&getPrivKey(0).PublicKey} granteeRef, _, _, _, _ = c.UpdateHandler(ctx, ls, ls, eglref, href, &publisher.PublicKey, addList, nil) - gl, err = dynamicaccess.NewGranteeListReference(ctx, ls, granteeRef) + gl, err = accesscontrol.NewGranteeListReference(ctx, ls, granteeRef) assert.NoError(t, err) assert.Len(t, gl.Get(), 2) }) t.Run("add and revoke", func(t *testing.T) { addList := []*ecdsa.PublicKey{&grantee.PublicKey} revokeList := []*ecdsa.PublicKey{&grantee1.PublicKey} - gl, _ := dynamicaccess.NewGranteeList(ls) + gl := accesscontrol.NewGranteeList(ls) _ = gl.Add([]*ecdsa.PublicKey{&publisher.PublicKey, &grantee1.PublicKey}) granteeRef, _ := gl.Save(ctx) eglref, _ := refCipher.Encrypt(granteeRef.Bytes()) granteeRef, _, _, _, _ = c.UpdateHandler(ctx, ls, gls, swarm.NewAddress(eglref), href, &publisher.PublicKey, addList, revokeList) - gl, err := dynamicaccess.NewGranteeListReference(ctx, ls, granteeRef) + gl, err := accesscontrol.NewGranteeListReference(ctx, ls, granteeRef) assert.NoError(t, err) assert.Len(t, gl.Get(), 2) @@ -223,15 +223,15 @@ func TestController_UpdateHandler(t *testing.T) { granteeRef, _, hrefUpdate2, _, err := c.UpdateHandler(ctx, ls, gls, egranteeRef, hrefUpdate1, &publisher.PublicKey, nil, addRevokeList) require.NoError(t, err) - gl, err := dynamicaccess.NewGranteeListReference(ctx, ls, granteeRef) + gl, err := accesscontrol.NewGranteeListReference(ctx, ls, granteeRef) require.NoError(t, err) assert.Empty(t, gl.Get()) // expect history reference to be different after grantee list update assert.NotEqual(t, hrefUpdate1, hrefUpdate2) - granteeDH := dynamicaccess.NewDefaultSession(grantee) - granteeAl := dynamicaccess.NewLogic(granteeDH) - granteeCtrl := dynamicaccess.NewController(granteeAl) + granteeDH := accesscontrol.NewDefaultSession(grantee) + granteeAl := accesscontrol.NewLogic(granteeDH) + granteeCtrl := accesscontrol.NewController(granteeAl) // download with grantee shall still work with the timestamp before the revoke decRef, err := granteeCtrl.DownloadHandler(ctx, ls, encRef, &publisher.PublicKey, hrefUpdate2, beforeRevokeTS) require.NoError(t, err) @@ -252,7 +252,7 @@ func TestController_UpdateHandler(t *testing.T) { //nolint:ineffassign,staticcheck,wastedassign granteeRef, eglref, _, _, err := c.UpdateHandler(ctx, ls, gls, swarm.ZeroAddress, href, &publisher.PublicKey, addList, nil) granteeRef, _, _, _, _ = c.UpdateHandler(ctx, ls, ls, eglref, href, &publisher.PublicKey, addList, nil) - gl, err := dynamicaccess.NewGranteeListReference(ctx, ls, granteeRef) + gl, err := accesscontrol.NewGranteeListReference(ctx, ls, granteeRef) assert.NoError(t, err) assert.Len(t, gl.Get(), 1) @@ -260,7 +260,7 @@ func TestController_UpdateHandler(t *testing.T) { t.Run("revoke non-existing", func(t *testing.T) { addList := []*ecdsa.PublicKey{&grantee.PublicKey} granteeRef, _, _, _, _ := c.UpdateHandler(ctx, ls, ls, swarm.ZeroAddress, href, &publisher.PublicKey, addList, nil) - gl, err := dynamicaccess.NewGranteeListReference(ctx, ls, granteeRef) + gl, err := accesscontrol.NewGranteeListReference(ctx, ls, granteeRef) assert.NoError(t, err) assert.Len(t, gl.Get(), 1) @@ -273,14 +273,14 @@ func TestController_Get(t *testing.T) { publisher := getPrivKey(1) caller := getPrivKey(0) grantee := getPrivKey(2) - diffieHellman1 := dynamicaccess.NewDefaultSession(publisher) - diffieHellman2 := dynamicaccess.NewDefaultSession(caller) - al1 := dynamicaccess.NewLogic(diffieHellman1) - al2 := dynamicaccess.NewLogic(diffieHellman2) + diffieHellman1 := accesscontrol.NewDefaultSession(publisher) + diffieHellman2 := accesscontrol.NewDefaultSession(caller) + al1 := accesscontrol.NewLogic(diffieHellman1) + al2 := accesscontrol.NewLogic(diffieHellman2) ls := createLs() gls := loadsave.New(mockStorer.ChunkStore(), mockStorer.Cache(), requestPipelineFactory(context.Background(), mockStorer.Cache(), true, redundancy.NONE)) - c1 := dynamicaccess.NewController(al1) - c2 := dynamicaccess.NewController(al2) + c1 := accesscontrol.NewController(al1) + c2 := accesscontrol.NewController(al2) t.Run("get by publisher", func(t *testing.T) { addList := []*ecdsa.PublicKey{&grantee.PublicKey} @@ -290,7 +290,7 @@ func TestController_Get(t *testing.T) { assert.NoError(t, err) assert.True(t, reflect.DeepEqual(grantees, addList)) - gl, _ := dynamicaccess.NewGranteeListReference(ctx, ls, granteeRef) + gl, _ := accesscontrol.NewGranteeListReference(ctx, ls, granteeRef) assert.True(t, reflect.DeepEqual(gl.Get(), addList)) }) t.Run("get by non-publisher", func(t *testing.T) { diff --git a/pkg/dynamicaccess/grantee.go b/pkg/accesscontrol/grantee.go similarity index 79% rename from pkg/dynamicaccess/grantee.go rename to pkg/accesscontrol/grantee.go index 44832032754..902aebbf433 100644 --- a/pkg/dynamicaccess/grantee.go +++ b/pkg/accesscontrol/grantee.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package dynamicaccess +package accesscontrol import ( "context" @@ -20,6 +20,15 @@ const ( publicKeyLen = 65 ) +var ( + // ErrNothingToRemove indicates that the remove list is empty. + ErrNothingToRemove = errors.New("nothing to remove") + // ErrNoGranteeFound indicates that the grantee list is empty. + ErrNoGranteeFound = errors.New("no grantee found") + // ErrNothingToAdd indicates that the add list is empty. + ErrNothingToAdd = errors.New("nothing to add") +) + // GranteeList manages a list of public keys. type GranteeList interface { // Add adds a list of public keys to the grantee list. It filters out duplicates. @@ -32,6 +41,7 @@ type GranteeList interface { Save(ctx context.Context) (swarm.Address, error) } +// GranteeListStruct represents a list of grantee public keys. type GranteeListStruct struct { grantees []*ecdsa.PublicKey loadSave file.LoadSaver @@ -39,13 +49,15 @@ type GranteeListStruct struct { var _ GranteeList = (*GranteeListStruct)(nil) +// Get simply returns the list of public keys. func (g *GranteeListStruct) Get() []*ecdsa.PublicKey { return g.grantees } +// Add adds a list of public keys to the grantee list. It filters out duplicates. func (g *GranteeListStruct) Add(addList []*ecdsa.PublicKey) error { if len(addList) == 0 { - return fmt.Errorf("no public key provided") + return ErrNothingToAdd } filteredList := make([]*ecdsa.PublicKey, 0, len(addList)) for _, addkey := range addList { @@ -71,6 +83,7 @@ func (g *GranteeListStruct) Add(addList []*ecdsa.PublicKey) error { return nil } +// Save saves the grantee list to the underlying storage and returns the reference. func (g *GranteeListStruct) Save(ctx context.Context) (swarm.Address, error) { data := serialize(g.grantees) refBytes, err := g.loadSave.Save(ctx, data) @@ -81,11 +94,7 @@ func (g *GranteeListStruct) Save(ctx context.Context) (swarm.Address, error) { return swarm.NewAddress(refBytes), nil } -var ( - ErrNothingToRemove = errors.New("nothing to remove") - ErrNoGranteeFound = errors.New("no grantee found") -) - +// Remove removes a list of public keys from the grantee list, if there is any. func (g *GranteeListStruct) Remove(keysToRemove []*ecdsa.PublicKey) error { if len(keysToRemove) == 0 { return ErrNothingToRemove @@ -109,17 +118,19 @@ func (g *GranteeListStruct) Remove(keysToRemove []*ecdsa.PublicKey) error { return nil } -func NewGranteeList(ls file.LoadSaver) (*GranteeListStruct, error) { // Why is the error necessary? +// NewGranteeList creates a new (and empty) grantee list. +func NewGranteeList(ls file.LoadSaver) *GranteeListStruct { return &GranteeListStruct{ grantees: []*ecdsa.PublicKey{}, loadSave: ls, - }, nil + } } +// NewGranteeListReference loads an existing grantee list. func NewGranteeListReference(ctx context.Context, ls file.LoadSaver, reference swarm.Address) (*GranteeListStruct, error) { data, err := ls.Load(ctx, reference.Bytes()) if err != nil { - return nil, fmt.Errorf("unable to load reference, %w", err) + return nil, fmt.Errorf("failed to load grantee list reference, %w", err) } grantees := deserialize(data) diff --git a/pkg/dynamicaccess/grantee_test.go b/pkg/accesscontrol/grantee_test.go similarity index 87% rename from pkg/dynamicaccess/grantee_test.go rename to pkg/accesscontrol/grantee_test.go index f9a9b0904f8..095029fb0ca 100644 --- a/pkg/dynamicaccess/grantee_test.go +++ b/pkg/accesscontrol/grantee_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package dynamicaccess_test +package accesscontrol_test import ( "context" @@ -11,7 +11,7 @@ import ( "testing" "github.com/btcsuite/btcd/btcec/v2" - "github.com/ethersphere/bee/v2/pkg/dynamicaccess" + "github.com/ethersphere/bee/v2/pkg/accesscontrol" "github.com/ethersphere/bee/v2/pkg/file" "github.com/ethersphere/bee/v2/pkg/file/loadsave" "github.com/ethersphere/bee/v2/pkg/file/pipeline" @@ -53,7 +53,7 @@ func generateKeyListFixture() ([]*ecdsa.PublicKey, error) { func TestGranteeAddGet(t *testing.T) { t.Parallel() - gl, _ := dynamicaccess.NewGranteeList(createLs()) + gl := accesscontrol.NewGranteeList(createLs()) keys, err := generateKeyListFixture() if err != nil { t.Errorf("key generation error: %v", err) @@ -66,10 +66,10 @@ func TestGranteeAddGet(t *testing.T) { t.Run("Get should return value equal to put value", func(t *testing.T) { var ( - keys2, _ = generateKeyListFixture() - addList1 []*ecdsa.PublicKey = []*ecdsa.PublicKey{keys[0]} - addList2 []*ecdsa.PublicKey = []*ecdsa.PublicKey{keys[1], keys[2]} - addList3 []*ecdsa.PublicKey = keys2 + keys2, _ = generateKeyListFixture() + addList1 = []*ecdsa.PublicKey{keys[0]} + addList2 = []*ecdsa.PublicKey{keys[1], keys[2]} + addList3 = keys2 ) testCases := []struct { name string @@ -118,7 +118,7 @@ func TestGranteeAddGet(t *testing.T) { func TestGranteeRemove(t *testing.T) { t.Parallel() - gl, _ := dynamicaccess.NewGranteeList(createLs()) + gl := accesscontrol.NewGranteeList(createLs()) keys, err := generateKeyListFixture() if err != nil { t.Errorf("key generation error: %v", err) @@ -172,17 +172,17 @@ func TestGranteeSave(t *testing.T) { t.Errorf("key generation error: %v", err) } t.Run("Create grantee list with invalid reference, expect error", func(t *testing.T) { - gl, err := dynamicaccess.NewGranteeListReference(ctx, createLs(), swarm.RandAddress(t)) + gl, err := accesscontrol.NewGranteeListReference(ctx, createLs(), swarm.RandAddress(t)) assert.Error(t, err) assert.Nil(t, gl) }) t.Run("Save empty grantee list return NO error", func(t *testing.T) { - gl, _ := dynamicaccess.NewGranteeList(createLs()) + gl := accesscontrol.NewGranteeList(createLs()) _, err := gl.Save(ctx) assert.NoError(t, err) }) t.Run("Save not empty grantee list return valid swarm address", func(t *testing.T) { - gl, _ := dynamicaccess.NewGranteeList(createLs()) + gl := accesscontrol.NewGranteeList(createLs()) err = gl.Add(keys) ref, err := gl.Save(ctx) assert.NoError(t, err) @@ -190,7 +190,7 @@ func TestGranteeSave(t *testing.T) { }) t.Run("Save grantee list with one item, no error, pre-save value exist", func(t *testing.T) { ls := createLs() - gl1, _ := dynamicaccess.NewGranteeList(ls) + gl1 := accesscontrol.NewGranteeList(ls) err := gl1.Add(keys) assert.NoError(t, err) @@ -198,7 +198,7 @@ func TestGranteeSave(t *testing.T) { ref, err := gl1.Save(ctx) assert.NoError(t, err) - gl2, _ := dynamicaccess.NewGranteeListReference(ctx, ls, ref) + gl2, _ := accesscontrol.NewGranteeListReference(ctx, ls, ref) val := gl2.Get() assert.NoError(t, err) assert.Equal(t, keys, val) @@ -207,14 +207,14 @@ func TestGranteeSave(t *testing.T) { ls := createLs() keys2, _ := generateKeyListFixture() - gl1, _ := dynamicaccess.NewGranteeList(ls) + gl1 := accesscontrol.NewGranteeList(ls) err := gl1.Add(keys) assert.NoError(t, err) ref, err := gl1.Save(ctx) assert.NoError(t, err) - gl2, _ := dynamicaccess.NewGranteeListReference(ctx, ls, ref) + gl2, _ := accesscontrol.NewGranteeListReference(ctx, ls, ref) err = gl2.Add(keys2) assert.NoError(t, err) @@ -224,7 +224,7 @@ func TestGranteeSave(t *testing.T) { } func TestGranteeRemoveTwo(t *testing.T) { - gl, _ := dynamicaccess.NewGranteeList(createLs()) + gl := accesscontrol.NewGranteeList(createLs()) keys, err := generateKeyListFixture() if err != nil { t.Errorf("key generation error: %v", err) diff --git a/pkg/dynamicaccess/history.go b/pkg/accesscontrol/history.go similarity index 74% rename from pkg/dynamicaccess/history.go rename to pkg/accesscontrol/history.go index 27485e95f95..a5a203834f2 100644 --- a/pkg/dynamicaccess/history.go +++ b/pkg/accesscontrol/history.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package dynamicaccess +package accesscontrol import ( "context" @@ -18,24 +18,34 @@ import ( "github.com/ethersphere/bee/v2/pkg/swarm" ) +var ( + // ErrEndIteration indicates that the iteration terminated. + ErrEndIteration = errors.New("end iteration") + // ErrUnexpectedType indicates that an error occurred during the mantary-manifest creation. + ErrUnexpectedType = errors.New("unexpected type") + // ErrInvalidTimestamp indicates that the timestamp given to Lookup is invalid. + ErrInvalidTimestamp = errors.New("invalid timestamp") +) + +// History represents the interface for managing access control history. type History interface { + // Add adds a new entry to the access control history with the given timestamp and metadata. Add(ctx context.Context, ref swarm.Address, timestamp *int64, metadata *map[string]string) error + // Lookup retrieves the entry from the history based on the given timestamp or returns error if not found. Lookup(ctx context.Context, timestamp int64) (manifest.Entry, error) + // Store stores the history to the underlying storage and returns the reference. Store(ctx context.Context) (swarm.Address, error) } var _ History = (*HistoryStruct)(nil) -var ( - ErrEndIteration = errors.New("end iteration") - ErrUnexpectedType = errors.New("unexpected type") -) - +// HistoryStruct represents an access control histroy with a mantaray-based manifest. type HistoryStruct struct { manifest *manifest.MantarayManifest ls file.LoadSaver } +// NewHistory creates a new history with a mantaray-based manifest. func NewHistory(ls file.LoadSaver) (*HistoryStruct, error) { m, err := manifest.NewDefaultManifest(ls, false) if err != nil { @@ -50,10 +60,11 @@ func NewHistory(ls file.LoadSaver) (*HistoryStruct, error) { return &HistoryStruct{manifest: mm, ls: ls}, nil } +// NewHistoryReference loads a history with a mantaray-based manifest. func NewHistoryReference(ls file.LoadSaver, ref swarm.Address) (*HistoryStruct, error) { m, err := manifest.NewDefaultManifestReference(ref, ls) if err != nil { - return nil, fmt.Errorf("failed to create default manifest: %w", err) + return nil, fmt.Errorf("failed to create default manifest reference: %w", err) } mm, ok := m.(*manifest.MantarayManifest) @@ -64,26 +75,28 @@ func NewHistoryReference(ls file.LoadSaver, ref swarm.Address) (*HistoryStruct, return &HistoryStruct{manifest: mm, ls: ls}, nil } +// Add adds a new entry to the access control history with the given timestamp and metadata. func (h *HistoryStruct) Add(ctx context.Context, ref swarm.Address, timestamp *int64, metadata *map[string]string) error { mtdt := map[string]string{} if metadata != nil { mtdt = *metadata } - // add timestamps transformed so that the latests timestamp becomes the smallest key - var unixTime int64 + // add timestamps transformed so that the latests timestamp becomes the smallest key. + unixTime := time.Now().Unix() if timestamp != nil { unixTime = *timestamp - } else { - unixTime = time.Now().Unix() } key := strconv.FormatInt(math.MaxInt64-unixTime, 10) - return h.manifest.Add(ctx, key, manifest.NewEntry(ref, mtdt)) -} + err := h.manifest.Add(ctx, key, manifest.NewEntry(ref, mtdt)) + if err != nil { + return fmt.Errorf("failed to add to manifest: %w", err) + } -var ErrInvalidTimestamp = errors.New("invalid timestamp") + return nil +} -// Lookup finds the entry for a path or returns error if not found +// Lookup retrieves the entry from the history based on the given timestamp or returns error if not found. func (h *HistoryStruct) Lookup(ctx context.Context, timestamp int64) (manifest.Entry, error) { if timestamp <= 0 { return manifest.NewEntry(swarm.ZeroAddress, map[string]string{}), ErrInvalidTimestamp @@ -147,6 +160,7 @@ func (h *HistoryStruct) lookupNode(ctx context.Context, searchedTimestamp int64) return nil, nil } +// Store stores the history to the underlying storage and returns the reference. func (h *HistoryStruct) Store(ctx context.Context) (swarm.Address, error) { return h.manifest.Store(ctx) } diff --git a/pkg/dynamicaccess/history_test.go b/pkg/accesscontrol/history_test.go similarity index 94% rename from pkg/dynamicaccess/history_test.go rename to pkg/accesscontrol/history_test.go index 871b5241eab..a9c1aaa939f 100644 --- a/pkg/dynamicaccess/history_test.go +++ b/pkg/accesscontrol/history_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package dynamicaccess_test +package accesscontrol_test import ( "context" @@ -10,7 +10,7 @@ import ( "testing" "time" - "github.com/ethersphere/bee/v2/pkg/dynamicaccess" + "github.com/ethersphere/bee/v2/pkg/accesscontrol" "github.com/ethersphere/bee/v2/pkg/file/loadsave" "github.com/ethersphere/bee/v2/pkg/file/pipeline" "github.com/ethersphere/bee/v2/pkg/file/pipeline/builder" @@ -22,7 +22,7 @@ import ( func TestHistoryAdd(t *testing.T) { t.Parallel() - h, err := dynamicaccess.NewHistory(nil) + h, err := accesscontrol.NewHistory(nil) assert.NoError(t, err) addr := swarm.NewAddress([]byte("addr")) @@ -39,7 +39,7 @@ func TestSingleNodeHistoryLookup(t *testing.T) { ctx := context.Background() ls := loadsave.New(storer.ChunkStore(), storer.Cache(), pipelineFactory(storer.Cache(), false)) - h, err := dynamicaccess.NewHistory(ls) + h, err := accesscontrol.NewHistory(ls) assert.NoError(t, err) testActRef := swarm.RandAddress(t) @@ -63,7 +63,7 @@ func TestMultiNodeHistoryLookup(t *testing.T) { ctx := context.Background() ls := loadsave.New(storer.ChunkStore(), storer.Cache(), pipelineFactory(storer.Cache(), false)) - h, _ := dynamicaccess.NewHistory(ls) + h, _ := accesscontrol.NewHistory(ls) testActRef1 := swarm.NewAddress([]byte("39a5ea87b141fe44aa609c3327ecd891")) firstTime := time.Date(1994, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() @@ -129,7 +129,7 @@ func TestHistoryStore(t *testing.T) { ctx := context.Background() ls := loadsave.New(storer.ChunkStore(), storer.Cache(), pipelineFactory(storer.Cache(), false)) - h1, _ := dynamicaccess.NewHistory(ls) + h1, _ := accesscontrol.NewHistory(ls) testActRef1 := swarm.NewAddress([]byte("39a5ea87b141fe44aa609c3327ecd891")) firstTime := time.Date(1994, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() @@ -139,7 +139,7 @@ func TestHistoryStore(t *testing.T) { href1, err := h1.Store(ctx) assert.NoError(t, err) - h2, err := dynamicaccess.NewHistoryReference(ls, href1) + h2, err := accesscontrol.NewHistoryReference(ls, href1) assert.NoError(t, err) entry1, _ := h2.Lookup(ctx, firstTime) diff --git a/pkg/kvs/kvs.go b/pkg/accesscontrol/kvs/kvs.go similarity index 58% rename from pkg/kvs/kvs.go rename to pkg/accesscontrol/kvs/kvs.go index 0c7eae48c08..6f5e35742b8 100644 --- a/pkg/kvs/kvs.go +++ b/pkg/accesscontrol/kvs/kvs.go @@ -2,21 +2,35 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// Package kvs provides functionalities needed +// for storing key-value pairs on Swarm. +// +//nolint:ireturn package kvs import ( "context" "encoding/hex" "errors" + "fmt" "github.com/ethersphere/bee/v2/pkg/file" "github.com/ethersphere/bee/v2/pkg/manifest" "github.com/ethersphere/bee/v2/pkg/swarm" ) +var ( + // ErrNothingToSave indicates that no new key-value pair was added to the store. + ErrNothingToSave = errors.New("nothing to save") +) + +// KeyValueStore represents a key-value store. type KeyValueStore interface { + // Get retrieves the value associated with the given key. Get(ctx context.Context, key []byte) ([]byte, error) + // Put stores the given key-value pair in the store. Put(ctx context.Context, key, value []byte) error + // Save saves key-value pair to the underlying storage and returns the reference. Save(ctx context.Context) (swarm.Address, error) } @@ -27,40 +41,44 @@ type keyValueStore struct { var _ KeyValueStore = (*keyValueStore)(nil) +// Get retrieves the value associated with the given key. func (s *keyValueStore) Get(ctx context.Context, key []byte) ([]byte, error) { entry, err := s.manifest.Lookup(ctx, hex.EncodeToString(key)) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to get value from manifest %w", err) } ref := entry.Reference() return ref.Bytes(), nil } +// Put stores the given key-value pair in the store. func (s *keyValueStore) Put(ctx context.Context, key []byte, value []byte) error { err := s.manifest.Add(ctx, hex.EncodeToString(key), manifest.NewEntry(swarm.NewAddress(value), map[string]string{})) if err != nil { - return err + return fmt.Errorf("failed to put value to manifest %w", err) } s.putCnt++ return nil } +// Save saves key-value pair to the underlying storage and returns the reference. func (s *keyValueStore) Save(ctx context.Context) (swarm.Address, error) { if s.putCnt == 0 { - return swarm.ZeroAddress, errors.New("nothing to save") + return swarm.ZeroAddress, ErrNothingToSave } ref, err := s.manifest.Store(ctx) if err != nil { - return swarm.ZeroAddress, err + return swarm.ZeroAddress, fmt.Errorf("failed to store manifest %w", err) } s.putCnt = 0 return ref, nil } +// New creates a new key-value store with a simple manifest. func New(ls file.LoadSaver) (KeyValueStore, error) { m, err := manifest.NewSimpleManifest(ls) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to create simple manifest: %w", err) } return &keyValueStore{ @@ -68,10 +86,11 @@ func New(ls file.LoadSaver) (KeyValueStore, error) { }, nil } +// NewReference loads a key-value store with a simple manifest. func NewReference(ls file.LoadSaver, ref swarm.Address) (KeyValueStore, error) { m, err := manifest.NewSimpleManifestReference(ref, ls) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to create simple manifest reference: %w", err) } return &keyValueStore{ diff --git a/pkg/kvs/kvs_test.go b/pkg/accesscontrol/kvs/kvs_test.go similarity index 96% rename from pkg/kvs/kvs_test.go rename to pkg/accesscontrol/kvs/kvs_test.go index 2f9af999a95..90abac1adfa 100644 --- a/pkg/kvs/kvs_test.go +++ b/pkg/accesscontrol/kvs/kvs_test.go @@ -2,24 +2,26 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//nolint:ireturn package kvs_test import ( "context" "testing" + "github.com/ethersphere/bee/v2/pkg/accesscontrol/kvs" "github.com/ethersphere/bee/v2/pkg/file" "github.com/ethersphere/bee/v2/pkg/file/loadsave" "github.com/ethersphere/bee/v2/pkg/file/pipeline" "github.com/ethersphere/bee/v2/pkg/file/pipeline/builder" "github.com/ethersphere/bee/v2/pkg/file/redundancy" - "github.com/ethersphere/bee/v2/pkg/kvs" "github.com/ethersphere/bee/v2/pkg/storage" mockstorer "github.com/ethersphere/bee/v2/pkg/storer/mock" "github.com/ethersphere/bee/v2/pkg/swarm" "github.com/stretchr/testify/assert" ) +//nolint:gochecknoglobals var mockStorer = mockstorer.New() func requestPipelineFactory(ctx context.Context, s storage.Putter, encrypt bool, rLevel redundancy.Level) func() pipeline.Interface { @@ -66,9 +68,9 @@ func TestKvs(t *testing.T) { t.Run("Get should return value equal to put value", func(t *testing.T) { var ( - key1 []byte = []byte{1} - key2 []byte = []byte{2} - key3 []byte = []byte{3} + key1 = []byte{1} + key2 = []byte{2} + key3 = []byte{3} ) testCases := []struct { name string diff --git a/pkg/kvs/mock/kvs.go b/pkg/accesscontrol/kvs/mock/kvs.go similarity index 93% rename from pkg/kvs/mock/kvs.go rename to pkg/accesscontrol/kvs/mock/kvs.go index f5818972004..bdd0d54cc12 100644 --- a/pkg/kvs/mock/kvs.go +++ b/pkg/accesscontrol/kvs/mock/kvs.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// Package mock provides an in-memory key-value store implementation. package mock import ( @@ -9,7 +10,7 @@ import ( "encoding/hex" "sync" - "github.com/ethersphere/bee/v2/pkg/kvs" + "github.com/ethersphere/bee/v2/pkg/accesscontrol/kvs" "github.com/ethersphere/bee/v2/pkg/swarm" ) diff --git a/pkg/dynamicaccess/mock/controller.go b/pkg/accesscontrol/mock/controller.go similarity index 88% rename from pkg/dynamicaccess/mock/controller.go rename to pkg/accesscontrol/mock/controller.go index 30f617bfccb..15e87e6ce0c 100644 --- a/pkg/dynamicaccess/mock/controller.go +++ b/pkg/accesscontrol/mock/controller.go @@ -2,6 +2,10 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// Package mock provides a mock implementation for the +// access control functionalities. +// +//nolint:ireturn package mock import ( @@ -11,8 +15,8 @@ import ( "fmt" "time" + "github.com/ethersphere/bee/v2/pkg/accesscontrol" "github.com/ethersphere/bee/v2/pkg/crypto" - "github.com/ethersphere/bee/v2/pkg/dynamicaccess" "github.com/ethersphere/bee/v2/pkg/encryption" "github.com/ethersphere/bee/v2/pkg/file" "github.com/ethersphere/bee/v2/pkg/file/loadsave" @@ -26,7 +30,7 @@ import ( ) type mockController struct { - historyMap map[string]dynamicaccess.History + historyMap map[string]accesscontrol.History refMap map[string]swarm.Address acceptAll bool publisher string @@ -36,18 +40,18 @@ type mockController struct { type optionFunc func(*mockController) -// Option is an option passed to a mock dynamicaccess Service. +// Option is an option passed to a mock accesscontrol Controller. type Option interface { apply(*mockController) } func (f optionFunc) apply(r *mockController) { f(r) } -// New creates a new mock dynamicaccess service. -func New(o ...Option) dynamicaccess.Controller { +// New creates a new mock accesscontrol Controller. +func New(o ...Option) accesscontrol.Controller { storer := mockstorer.New() m := &mockController{ - historyMap: make(map[string]dynamicaccess.History), + historyMap: make(map[string]accesscontrol.History), refMap: make(map[string]swarm.Address), publisher: "", encrypter: encryption.New(encryption.Key("b6ee086390c280eeb9824c331a4427596f0c8510d5564bc1b6168d0059a46e2b"), 0, uint32(0), sha3.NewLegacyKeccak256), @@ -65,12 +69,14 @@ func WithAcceptAll() Option { return optionFunc(func(m *mockController) { m.acceptAll = true }) } -func WithHistory(h dynamicaccess.History, ref string) Option { +// WithHistory sets the mock to use the given history reference. +func WithHistory(h accesscontrol.History, ref string) Option { return optionFunc(func(m *mockController) { - m.historyMap = map[string]dynamicaccess.History{ref: h} + m.historyMap = map[string]accesscontrol.History{ref: h} }) } +// WithPublisher sets the mock to use the given reference as the publisher address. func WithPublisher(ref string) Option { return optionFunc(func(m *mockController) { m.publisher = ref @@ -109,7 +115,7 @@ func (m *mockController) UploadHandler(ctx context.Context, ls file.LoadSaver, r return kvsRef, historyRef, encryptedRef, nil } var ( - h dynamicaccess.History + h accesscontrol.History exists bool ) now := time.Now().Unix() @@ -125,7 +131,7 @@ func (m *mockController) UploadHandler(ctx context.Context, ls file.LoadSaver, r return swarm.ZeroAddress, swarm.ZeroAddress, swarm.ZeroAddress, fmt.Errorf("kvs not found") } } else { - h, _ = dynamicaccess.NewHistory(m.ls) + h, _ = accesscontrol.NewHistory(m.ls) _ = h.Add(ctx, kvsRef, &now, nil) historyRef, _ = h.Store(ctx) m.historyMap[historyRef.String()] = h @@ -180,4 +186,4 @@ func requestPipelineFactory(ctx context.Context, s storage.Putter, encrypt bool, } } -var _ dynamicaccess.Controller = (*mockController)(nil) +var _ accesscontrol.Controller = (*mockController)(nil) diff --git a/pkg/dynamicaccess/mock/grantee.go b/pkg/accesscontrol/mock/grantee.go similarity index 100% rename from pkg/dynamicaccess/mock/grantee.go rename to pkg/accesscontrol/mock/grantee.go diff --git a/pkg/dynamicaccess/mock/session.go b/pkg/accesscontrol/mock/session.go similarity index 100% rename from pkg/dynamicaccess/mock/session.go rename to pkg/accesscontrol/mock/session.go diff --git a/pkg/dynamicaccess/session.go b/pkg/accesscontrol/session.go similarity index 69% rename from pkg/dynamicaccess/session.go rename to pkg/accesscontrol/session.go index fb1f44241be..a571337c17d 100644 --- a/pkg/dynamicaccess/session.go +++ b/pkg/accesscontrol/session.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package dynamicaccess +package accesscontrol import ( "crypto/ecdsa" @@ -10,26 +10,29 @@ import ( "fmt" "github.com/ethersphere/bee/v2/pkg/crypto" - "github.com/ethersphere/bee/v2/pkg/keystore" ) -// Session represents an interface for a Diffie-Helmann key derivation +var ( + // ErrInvalidPublicKey is an error that is returned when a public key is nil. + ErrInvalidPublicKey = errors.New("invalid public key") + // ErrSecretKeyInfinity is an error that is returned when the shared secret is a point at infinity. + ErrSecretKeyInfinity = errors.New("shared secret is point at infinity") +) + +// Session represents an interface for a Diffie-Hellmann key derivation type Session interface { - // Key returns a derived key for each nonce + // Key returns a derived key for each nonce. Key(publicKey *ecdsa.PublicKey, nonces [][]byte) ([][]byte, error) } var _ Session = (*SessionStruct)(nil) +// SessionStruct represents a session with an access control key. type SessionStruct struct { key *ecdsa.PrivateKey } -var ( - ErrInvalidPublicKey = errors.New("invalid public key") - ErrSecretKeyInfinity = errors.New("shared secret is point at infinity") -) - +// Key returns a derived key for each nonce. func (s *SessionStruct) Key(publicKey *ecdsa.PublicKey, nonces [][]byte) ([][]byte, error) { if publicKey == nil { return nil, ErrInvalidPublicKey @@ -55,13 +58,9 @@ func (s *SessionStruct) Key(publicKey *ecdsa.PublicKey, nonces [][]byte) ([][]by return keys, nil } +// NewDefaultSession creates a new session from a private key. func NewDefaultSession(key *ecdsa.PrivateKey) *SessionStruct { return &SessionStruct{ key: key, } } - -// Currently implemented only in mock/session.go -func NewFromKeystore(keystore.Service, string, string) Session { - return nil -} diff --git a/pkg/dynamicaccess/session_test.go b/pkg/accesscontrol/session_test.go similarity index 84% rename from pkg/dynamicaccess/session_test.go rename to pkg/accesscontrol/session_test.go index 701384b93bb..60b175b01a3 100644 --- a/pkg/dynamicaccess/session_test.go +++ b/pkg/accesscontrol/session_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package dynamicaccess_test +package accesscontrol_test import ( "bytes" @@ -12,9 +12,9 @@ import ( "io" "testing" + "github.com/ethersphere/bee/v2/pkg/accesscontrol" + "github.com/ethersphere/bee/v2/pkg/accesscontrol/mock" "github.com/ethersphere/bee/v2/pkg/crypto" - "github.com/ethersphere/bee/v2/pkg/dynamicaccess" - "github.com/ethersphere/bee/v2/pkg/dynamicaccess/mock" memkeystore "github.com/ethersphere/bee/v2/pkg/keystore/mem" ) @@ -27,7 +27,7 @@ func TestSessionNewDefaultSession(t *testing.T) { if err != nil { t.Fatalf("Error generating private key: %v", err) } - si := dynamicaccess.NewDefaultSession(pk) + si := accesscontrol.NewDefaultSession(pk) if si == nil { t.Fatal("Session instance is nil") } @@ -48,13 +48,13 @@ func TestSessionKey(t *testing.T) { if err != nil { t.Fatal(err) } - si1 := dynamicaccess.NewDefaultSession(key1) + si1 := accesscontrol.NewDefaultSession(key1) key2, err := crypto.GenerateSecp256k1Key() if err != nil { t.Fatal(err) } - si2 := dynamicaccess.NewDefaultSession(key2) + si2 := accesscontrol.NewDefaultSession(key2) nonces := make([][]byte, 2) for i := range nonces { @@ -87,13 +87,13 @@ func TestSessionKeyWithoutNonces(t *testing.T) { if err != nil { t.Fatal(err) } - si1 := dynamicaccess.NewDefaultSession(key1) + si1 := accesscontrol.NewDefaultSession(key1) key2, err := crypto.GenerateSecp256k1Key() if err != nil { t.Fatal(err) } - si2 := dynamicaccess.NewDefaultSession(key2) + si2 := accesscontrol.NewDefaultSession(key2) keys1, err := si1.Key(&key2.PublicKey, nil) if err != nil { @@ -119,7 +119,6 @@ func TestSessionKeyFromKeystore(t *testing.T) { password2 := "password2" si1 := mock.NewFromKeystore(ks, tag1, password1, mockKeyFunc) - // si1 := dynamicaccess.NewFromKeystore(ks, tag1, password1) exists, err := ks.Exists(tag1) if err != nil { t.Fatal(err) @@ -136,7 +135,6 @@ func TestSessionKeyFromKeystore(t *testing.T) { } si2 := mock.NewFromKeystore(ks, tag2, password2, mockKeyFunc) - // si2 := dynamicaccess.NewFromKeystore(ks, tag2, password2) exists, err = ks.Exists(tag2) if err != nil { t.Fatal(err) @@ -171,7 +169,4 @@ func TestSessionKeyFromKeystore(t *testing.T) { if !bytes.Equal(keys1[0], keys2[0]) { t.Fatalf("shared secrets do not match %s, %s", hex.EncodeToString(keys1[0]), hex.EncodeToString(keys2[0])) } - // if !bytes.Equal(keys1[1], keys2[1]) { - // t.Fatalf("shared secrets do not match %s, %s", hex.EncodeToString(keys1[0]), hex.EncodeToString(keys2[0])) - // } } diff --git a/pkg/api/dynamicaccess.go b/pkg/api/accesscontrol.go similarity index 87% rename from pkg/api/dynamicaccess.go rename to pkg/api/accesscontrol.go index f83f0cca435..11c0453014c 100644 --- a/pkg/api/dynamicaccess.go +++ b/pkg/api/accesscontrol.go @@ -31,7 +31,7 @@ type addressKey struct{} const granteeListEncrypt = true -// getAddressFromContext is a helper function to extract the address from the context +// getAddressFromContext is a helper function to extract the address from the context. func getAddressFromContext(ctx context.Context) swarm.Address { v, ok := ctx.Value(addressKey{}).(swarm.Address) if ok { @@ -40,37 +40,52 @@ func getAddressFromContext(ctx context.Context) swarm.Address { return swarm.ZeroAddress } -// setAddressInContext sets the swarm address in the context +// setAddressInContext sets the swarm address in the context. func setAddressInContext(ctx context.Context, address swarm.Address) context.Context { return context.WithValue(ctx, addressKey{}, address) } +// GranteesPatchRequest represents a request to patch the list of grantees. type GranteesPatchRequest struct { - Addlist []string `json:"add"` + // Addlist contains the list of grantees to add. + Addlist []string `json:"add"` + + // Revokelist contains the list of grantees to revoke. Revokelist []string `json:"revoke"` } +// GranteesPatchResponse represents the response structure for patching grantees. type GranteesPatchResponse struct { - Reference swarm.Address `json:"ref"` + // Reference represents the swarm address. + Reference swarm.Address `json:"ref"` + // HistoryReference represents the reference to the history of an access control entry. HistoryReference swarm.Address `json:"historyref"` } +// GranteesPostRequest represents the request structure for adding grantees. type GranteesPostRequest struct { + // GranteeList represents the list of grantees to be saves on Swarm. GranteeList []string `json:"grantees"` } +// GranteesPostResponse represents the response structure for adding grantees. type GranteesPostResponse struct { - Reference swarm.Address `json:"ref"` + // Reference represents the saved grantee list Swarm address. + Reference swarm.Address `json:"ref"` + // HistoryReference represents the reference to the history of an access control entry. HistoryReference swarm.Address `json:"historyref"` } +// GranteesPatch represents a structure for modifying the list of grantees. type GranteesPatch struct { - Addlist []*ecdsa.PublicKey + // Addlist is a list of ecdsa.PublicKeys to be added to a grantee list. + Addlist []*ecdsa.PublicKey + // Revokelist is a list of ecdsa.PublicKeys to be removed from a grantee list Revokelist []*ecdsa.PublicKey } // actDecryptionHandler is a middleware that looks up and decrypts the given address, -// if the act headers are present +// if the act headers are present. func (s *Service) actDecryptionHandler() func(h http.Handler) http.Handler { return func(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -111,7 +126,7 @@ func (s *Service) actDecryptionHandler() func(h http.Handler) http.Handler { } ctx := r.Context() ls := loadsave.NewReadonly(s.storer.Download(cache)) - reference, err := s.dac.DownloadHandler(ctx, ls, paths.Address, headers.Publisher, *headers.HistoryAddress, timestamp) + reference, err := s.accesscontrol.DownloadHandler(ctx, ls, paths.Address, headers.Publisher, *headers.HistoryAddress, timestamp) if err != nil { logger.Debug("act failed to decrypt reference", "error", err) logger.Error(nil, "act failed to decrypt reference") @@ -124,7 +139,7 @@ func (s *Service) actDecryptionHandler() func(h http.Handler) http.Handler { } // actEncryptionHandler is a middleware that encrypts the given address using the publisher's public key, -// uploads the encrypted reference, history and kvs to the store +// uploads the encrypted reference, history and kvs to the store. func (s *Service) actEncryptionHandler( ctx context.Context, w http.ResponseWriter, @@ -135,7 +150,7 @@ func (s *Service) actEncryptionHandler( logger := s.logger.WithName("act_encryption_handler").Build() publisherPublicKey := &s.publicKey ls := loadsave.New(s.storer.Download(true), s.storer.Cache(), requestPipelineFactory(ctx, putter, false, redundancy.NONE)) - storageReference, historyReference, encryptedReference, err := s.dac.UploadHandler(ctx, ls, reference, publisherPublicKey, historyRootHash) + storageReference, historyReference, encryptedReference, err := s.accesscontrol.UploadHandler(ctx, ls, reference, publisherPublicKey, historyRootHash) if err != nil { logger.Debug("act failed to encrypt reference", "error", err) logger.Error(nil, "act failed to encrypt reference") @@ -163,7 +178,7 @@ func (s *Service) actEncryptionHandler( } // actListGranteesHandler is a middleware that decrypts the given address and returns the list of grantees, -// only the publisher is authorized to access the list +// only the publisher is authorized to access the list. func (s *Service) actListGranteesHandler(w http.ResponseWriter, r *http.Request) { logger := s.logger.WithName("act_list_grantees_handler").Build() paths := struct { @@ -187,7 +202,7 @@ func (s *Service) actListGranteesHandler(w http.ResponseWriter, r *http.Request) } publisher := &s.publicKey ls := loadsave.NewReadonly(s.storer.Download(cache)) - grantees, err := s.dac.Get(r.Context(), ls, publisher, paths.GranteesAddress) + grantees, err := s.accesscontrol.Get(r.Context(), ls, publisher, paths.GranteesAddress) if err != nil { logger.Debug("could not get grantees", "error", err) logger.Error(nil, "could not get grantees") @@ -202,7 +217,7 @@ func (s *Service) actListGranteesHandler(w http.ResponseWriter, r *http.Request) } // actGrantRevokeHandler is a middleware that makes updates to the list of grantees, -// only the publisher is authorized to perform this action +// only the publisher is authorized to perform this action. func (s *Service) actGrantRevokeHandler(w http.ResponseWriter, r *http.Request) { logger := s.logger.WithName("act_grant_revoke_handler").Build() @@ -328,7 +343,7 @@ func (s *Service) actGrantRevokeHandler(w http.ResponseWriter, r *http.Request) publisher := &s.publicKey ls := loadsave.New(s.storer.Download(true), s.storer.Cache(), requestPipelineFactory(ctx, putter, false, redundancy.NONE)) gls := loadsave.New(s.storer.Download(true), s.storer.Cache(), requestPipelineFactory(ctx, putter, granteeListEncrypt, redundancy.NONE)) - granteeref, encryptedglref, historyref, actref, err := s.dac.UpdateHandler(ctx, ls, gls, granteeref, historyAddress, publisher, grantees.Addlist, grantees.Revokelist) + granteeref, encryptedglref, historyref, actref, err := s.accesscontrol.UpdateHandler(ctx, ls, gls, granteeref, historyAddress, publisher, grantees.Addlist, grantees.Revokelist) if err != nil { logger.Debug("failed to update grantee list", "error", err) logger.Error(nil, "failed to update grantee list") @@ -367,7 +382,7 @@ func (s *Service) actGrantRevokeHandler(w http.ResponseWriter, r *http.Request) } // actCreateGranteesHandler is a middleware that creates a new list of grantees, -// only the publisher is authorized to perform this action +// only the publisher is authorized to perform this action. func (s *Service) actCreateGranteesHandler(w http.ResponseWriter, r *http.Request) { logger := s.logger.WithName("acthandler").Build() @@ -473,7 +488,7 @@ func (s *Service) actCreateGranteesHandler(w http.ResponseWriter, r *http.Reques publisher := &s.publicKey ls := loadsave.New(s.storer.Download(true), s.storer.Cache(), requestPipelineFactory(ctx, putter, false, redundancy.NONE)) gls := loadsave.New(s.storer.Download(true), s.storer.Cache(), requestPipelineFactory(ctx, putter, granteeListEncrypt, redundancy.NONE)) - granteeref, encryptedglref, historyref, actref, err := s.dac.UpdateHandler(ctx, ls, gls, swarm.ZeroAddress, historyAddress, publisher, list, nil) + granteeref, encryptedglref, historyref, actref, err := s.accesscontrol.UpdateHandler(ctx, ls, gls, swarm.ZeroAddress, historyAddress, publisher, list, nil) if err != nil { logger.Debug("failed to update grantee list", "error", err) logger.Error(nil, "failed to update grantee list") @@ -516,11 +531,11 @@ func parseKeys(list []string) ([]*ecdsa.PublicKey, error) { for _, g := range list { h, err := hex.DecodeString(g) if err != nil { - return []*ecdsa.PublicKey{}, err + return []*ecdsa.PublicKey{}, fmt.Errorf("failed to decode grantee: %w", err) } k, err := btcec.ParsePubKey(h) if err != nil { - return []*ecdsa.PublicKey{}, err + return []*ecdsa.PublicKey{}, fmt.Errorf("failed to parse grantee public key: %w", err) } parsedList = append(parsedList, k.ToECDSA()) } diff --git a/pkg/api/dynamicaccess_test.go b/pkg/api/accesscontrol_test.go similarity index 89% rename from pkg/api/dynamicaccess_test.go rename to pkg/api/accesscontrol_test.go index 79eac36327b..11b3e02f28e 100644 --- a/pkg/api/dynamicaccess_test.go +++ b/pkg/api/accesscontrol_test.go @@ -16,10 +16,10 @@ import ( "testing" "time" + "github.com/ethersphere/bee/v2/pkg/accesscontrol" + mockac "github.com/ethersphere/bee/v2/pkg/accesscontrol/mock" "github.com/ethersphere/bee/v2/pkg/api" "github.com/ethersphere/bee/v2/pkg/crypto" - "github.com/ethersphere/bee/v2/pkg/dynamicaccess" - mockdac "github.com/ethersphere/bee/v2/pkg/dynamicaccess/mock" "github.com/ethersphere/bee/v2/pkg/file/loadsave" "github.com/ethersphere/bee/v2/pkg/file/redundancy" "github.com/ethersphere/bee/v2/pkg/jsonhttp" @@ -32,11 +32,12 @@ import ( "gitlab.com/nolash/go-mockbytes" ) -func prepareHistoryFixture(storer api.Storer) (dynamicaccess.History, swarm.Address) { +//nolint:ireturn +func prepareHistoryFixture(storer api.Storer) (accesscontrol.History, swarm.Address) { ctx := context.Background() ls := loadsave.New(storer.ChunkStore(), storer.Cache(), pipelineFactory(storer.Cache(), false, redundancy.NONE)) - h, _ := dynamicaccess.NewHistory(ls) + h, _ := accesscontrol.NewHistory(ls) testActRef1 := swarm.NewAddress([]byte("39a5ea87b141fe44aa609c3327ecd891")) firstTime := time.Date(1994, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() @@ -65,9 +66,9 @@ func prepareHistoryFixture(storer api.Storer) (dynamicaccess.History, swarm.Addr // TODO: test tag, pin, deferred, stamp // TODO: feed test // nolint:paralleltest,tparallel -// TestDacWithoutActHeader [positive tests]: +// TestAccessLogicEachEndpointWithAct [positive tests]: // On each endpoint: upload w/ "Swarm-Act" header then download and check the decrypted data -func TestDacEachEndpointWithAct(t *testing.T) { +func TestAccessLogicEachEndpointWithAct(t *testing.T) { t.Parallel() var ( spk, _ = hex.DecodeString("a786dd84b61485de12146fd9c4c02d87e8fd95f0542765cb7fc3d2e428c0bcfa") @@ -185,11 +186,11 @@ func TestDacEachEndpointWithAct(t *testing.T) { } t.Run(v.name, func(t *testing.T) { client, _, _, _ := newTestServer(t, testServerOptions{ - Storer: storerMock, - Logger: logger, - Post: mockpost.New(mockpost.WithAcceptAll()), - PublicKey: pk.PublicKey, - Dac: mockdac.New(), + Storer: storerMock, + Logger: logger, + Post: mockpost.New(mockpost.WithAcceptAll()), + PublicKey: pk.PublicKey, + AccessControl: mockac.New(), }) header := jsonhttptest.Request(t, client, http.MethodPost, v.upurl, http.StatusCreated, upTestOpts..., @@ -221,12 +222,12 @@ func TestDacEachEndpointWithAct(t *testing.T) { } } -// TestDacWithoutActHeader [negative tests]: +// TestAccessLogicWithoutActHeader [negative tests]: // 1. upload w/ "Swarm-Act" header then try to dowload w/o the header. // 2. upload w/o "Swarm-Act" header then try to dowload w/ the header. // //nolint:paralleltest,tparallel -func TestDacWithoutAct(t *testing.T) { +func TestAccessLogicWithoutAct(t *testing.T) { t.Parallel() var ( spk, _ = hex.DecodeString("a786dd84b61485de12146fd9c4c02d87e8fd95f0542765cb7fc3d2e428c0bcfa") @@ -244,11 +245,11 @@ func TestDacWithoutAct(t *testing.T) { t.Run("upload-w/-act-then-download-w/o-act", func(t *testing.T) { client, _, _, _ := newTestServer(t, testServerOptions{ - Storer: storerMock, - Logger: logger, - Post: mockpost.New(mockpost.WithAcceptAll()), - PublicKey: pk.PublicKey, - Dac: mockdac.New(mockdac.WithHistory(h, fixtureHref.String())), + Storer: storerMock, + Logger: logger, + Post: mockpost.New(mockpost.WithAcceptAll()), + PublicKey: pk.PublicKey, + AccessControl: mockac.New(mockac.WithHistory(h, fixtureHref.String())), }) var ( testfile = "testfile1" @@ -277,11 +278,11 @@ func TestDacWithoutAct(t *testing.T) { t.Run("upload-w/o-act-then-download-w/-act", func(t *testing.T) { client, _, _, _ := newTestServer(t, testServerOptions{ - Storer: storerMock, - Logger: logger, - Post: mockpost.New(mockpost.WithAcceptAll()), - PublicKey: pk.PublicKey, - Dac: mockdac.New(), + Storer: storerMock, + Logger: logger, + Post: mockpost.New(mockpost.WithAcceptAll()), + PublicKey: pk.PublicKey, + AccessControl: mockac.New(), }) var ( rootHash = "0cb947ccbc410c43139ba4409d83bf89114cb0d79556a651c06c888cf73f4d7e" @@ -322,10 +323,10 @@ func TestDacWithoutAct(t *testing.T) { }) } -// TestDacInvalidPath [negative test]: Expect Bad request when the path address is invalid. +// TestAccessLogicInvalidPath [negative test]: Expect Bad request when the path address is invalid. // //nolint:paralleltest,tparallel -func TestDacInvalidPath(t *testing.T) { +func TestAccessLogicInvalidPath(t *testing.T) { t.Parallel() var ( spk, _ = hex.DecodeString("a786dd84b61485de12146fd9c4c02d87e8fd95f0542765cb7fc3d2e428c0bcfa") @@ -341,11 +342,11 @@ func TestDacInvalidPath(t *testing.T) { t.Run("invalid-path-params", func(t *testing.T) { client, _, _, _ := newTestServer(t, testServerOptions{ - Storer: storerMock, - Logger: logger, - Post: mockpost.New(mockpost.WithAcceptAll()), - PublicKey: pk.PublicKey, - Dac: mockdac.New(), + Storer: storerMock, + Logger: logger, + Post: mockpost.New(mockpost.WithAcceptAll()), + PublicKey: pk.PublicKey, + AccessControl: mockac.New(), }) encryptedRef := "asd" @@ -369,12 +370,12 @@ func TestDacInvalidPath(t *testing.T) { } // nolint:paralleltest,tparallel -// TestDacHistory tests: +// TestAccessLogicHistory tests: // [positive tests] 1., 2.: uploading a file w/ and w/o history address then downloading it and checking the data. // [negative test] 3. uploading a file then downloading it with a wrong history address. // [negative test] 4. uploading a file to a wrong history address. // [negative test] 5. downloading a file to w/o history address. -func TestDacHistory(t *testing.T) { +func TestAccessLogicHistory(t *testing.T) { t.Parallel() var ( spk, _ = hex.DecodeString("a786dd84b61485de12146fd9c4c02d87e8fd95f0542765cb7fc3d2e428c0bcfa") @@ -392,11 +393,11 @@ func TestDacHistory(t *testing.T) { t.Run("empty-history-upload-then-download-and-check-data", func(t *testing.T) { client, _, _, _ := newTestServer(t, testServerOptions{ - Storer: storerMock, - Logger: logger, - Post: mockpost.New(mockpost.WithAcceptAll()), - PublicKey: pk.PublicKey, - Dac: mockdac.New(), + Storer: storerMock, + Logger: logger, + Post: mockpost.New(mockpost.WithAcceptAll()), + PublicKey: pk.PublicKey, + AccessControl: mockac.New(), }) var ( testfile = "testfile1" @@ -428,11 +429,11 @@ func TestDacHistory(t *testing.T) { t.Run("with-history-upload-then-download-and-check-data", func(t *testing.T) { client, _, _, _ := newTestServer(t, testServerOptions{ - Storer: storerMock, - Logger: logger, - Post: mockpost.New(mockpost.WithAcceptAll()), - PublicKey: pk.PublicKey, - Dac: mockdac.New(mockdac.WithHistory(h, fixtureHref.String())), + Storer: storerMock, + Logger: logger, + Post: mockpost.New(mockpost.WithAcceptAll()), + PublicKey: pk.PublicKey, + AccessControl: mockac.New(mockac.WithHistory(h, fixtureHref.String())), }) var ( encryptedRef = "c611199e1b3674d6bf89a83e518bd16896bf5315109b4a23dcb4682a02d17b97" @@ -474,11 +475,11 @@ func TestDacHistory(t *testing.T) { t.Run("upload-then-download-wrong-history", func(t *testing.T) { client, _, _, _ := newTestServer(t, testServerOptions{ - Storer: storerMock, - Logger: logger, - Post: mockpost.New(mockpost.WithAcceptAll()), - PublicKey: pk.PublicKey, - Dac: mockdac.New(mockdac.WithHistory(h, fixtureHref.String())), + Storer: storerMock, + Logger: logger, + Post: mockpost.New(mockpost.WithAcceptAll()), + PublicKey: pk.PublicKey, + AccessControl: mockac.New(mockac.WithHistory(h, fixtureHref.String())), }) var ( testfile = "testfile1" @@ -510,11 +511,11 @@ func TestDacHistory(t *testing.T) { t.Run("upload-wrong-history", func(t *testing.T) { client, _, _, _ := newTestServer(t, testServerOptions{ - Storer: storerMock, - Logger: logger, - Post: mockpost.New(mockpost.WithAcceptAll()), - PublicKey: pk.PublicKey, - Dac: mockdac.New(), + Storer: storerMock, + Logger: logger, + Post: mockpost.New(mockpost.WithAcceptAll()), + PublicKey: pk.PublicKey, + AccessControl: mockac.New(), }) testfile := "testfile1" @@ -533,11 +534,11 @@ func TestDacHistory(t *testing.T) { t.Run("download-w/o-history", func(t *testing.T) { client, _, _, _ := newTestServer(t, testServerOptions{ - Storer: storerMock, - Logger: logger, - Post: mockpost.New(mockpost.WithAcceptAll()), - PublicKey: pk.PublicKey, - Dac: mockdac.New(mockdac.WithHistory(h, fixtureHref.String())), + Storer: storerMock, + Logger: logger, + Post: mockpost.New(mockpost.WithAcceptAll()), + PublicKey: pk.PublicKey, + AccessControl: mockac.New(mockac.WithHistory(h, fixtureHref.String())), }) encryptedRef := "a5df670544eaea29e61b19d8739faa4573b19e4426e58a173e51ed0b5e7e2ade" @@ -550,10 +551,10 @@ func TestDacHistory(t *testing.T) { } // nolint:paralleltest,tparallel -// TestDacTimestamp +// TestAccessLogicTimestamp // [positive test] 1.: uploading a file w/ ACT then download it w/ timestamp and check the data. // [negative test] 2.: try to download a file w/o timestamp. -func TestDacTimestamp(t *testing.T) { +func TestAccessLogicTimestamp(t *testing.T) { t.Parallel() var ( spk, _ = hex.DecodeString("a786dd84b61485de12146fd9c4c02d87e8fd95f0542765cb7fc3d2e428c0bcfa") @@ -569,11 +570,11 @@ func TestDacTimestamp(t *testing.T) { ) t.Run("upload-then-download-with-timestamp-and-check-data", func(t *testing.T) { client, _, _, _ := newTestServer(t, testServerOptions{ - Storer: storerMock, - Logger: logger, - Post: mockpost.New(mockpost.WithAcceptAll()), - PublicKey: pk.PublicKey, - Dac: mockdac.New(mockdac.WithHistory(h, fixtureHref.String())), + Storer: storerMock, + Logger: logger, + Post: mockpost.New(mockpost.WithAcceptAll()), + PublicKey: pk.PublicKey, + AccessControl: mockac.New(mockac.WithHistory(h, fixtureHref.String())), }) var ( thirdTime = time.Date(2015, time.April, 1, 0, 0, 0, 0, time.UTC).Unix() @@ -617,11 +618,11 @@ func TestDacTimestamp(t *testing.T) { t.Run("download-w/o-timestamp", func(t *testing.T) { encryptedRef := "a5df670544eaea29e61b19d8739faa4573b19e4426e58a173e51ed0b5e7e2ade" client, _, _, _ := newTestServer(t, testServerOptions{ - Storer: storerMock, - Logger: logger, - Post: mockpost.New(mockpost.WithAcceptAll()), - PublicKey: pk.PublicKey, - Dac: mockdac.New(mockdac.WithHistory(h, fixtureHref.String())), + Storer: storerMock, + Logger: logger, + Post: mockpost.New(mockpost.WithAcceptAll()), + PublicKey: pk.PublicKey, + AccessControl: mockac.New(mockac.WithHistory(h, fixtureHref.String())), }) jsonhttptest.Request(t, client, http.MethodGet, fileDownloadResource(encryptedRef), http.StatusNotFound, @@ -633,12 +634,12 @@ func TestDacTimestamp(t *testing.T) { } // nolint:paralleltest,tparallel -// TestDacPublisher +// TestAccessLogicPublisher // [positive test] 1.: uploading a file w/ ACT then download it w/ the publisher address and check the data. // [negative test] 2.: expect Bad request when the public key is invalid. // [negative test] 3.: try to download a file w/ an incorrect publisher address. // [negative test] 3.: try to download a file w/o a publisher address. -func TestDacPublisher(t *testing.T) { +func TestAccessLogicPublisher(t *testing.T) { t.Parallel() var ( spk, _ = hex.DecodeString("a786dd84b61485de12146fd9c4c02d87e8fd95f0542765cb7fc3d2e428c0bcfa") @@ -656,11 +657,11 @@ func TestDacPublisher(t *testing.T) { t.Run("upload-then-download-w/-publisher-and-check-data", func(t *testing.T) { client, _, _, _ := newTestServer(t, testServerOptions{ - Storer: storerMock, - Logger: logger, - Post: mockpost.New(mockpost.WithAcceptAll()), - PublicKey: pk.PublicKey, - Dac: mockdac.New(mockdac.WithHistory(h, fixtureHref.String()), mockdac.WithPublisher(publisher)), + Storer: storerMock, + Logger: logger, + Post: mockpost.New(mockpost.WithAcceptAll()), + PublicKey: pk.PublicKey, + AccessControl: mockac.New(mockac.WithHistory(h, fixtureHref.String()), mockac.WithPublisher(publisher)), }) var ( encryptedRef = "a5a26b4915d7ce1622f9ca52252092cf2445f98d359dabaf52588c05911aaf4f" @@ -702,11 +703,11 @@ func TestDacPublisher(t *testing.T) { t.Run("upload-then-download-invalid-publickey", func(t *testing.T) { client, _, _, _ := newTestServer(t, testServerOptions{ - Storer: storerMock, - Logger: logger, - Post: mockpost.New(mockpost.WithAcceptAll()), - PublicKey: pk.PublicKey, - Dac: mockdac.New(mockdac.WithPublisher(publisher)), + Storer: storerMock, + Logger: logger, + Post: mockpost.New(mockpost.WithAcceptAll()), + PublicKey: pk.PublicKey, + AccessControl: mockac.New(mockac.WithPublisher(publisher)), }) var ( publickey = "b786dd84b61485de12146fd9c4c02d87e8fd95f0542765cb7fc3d2e428c0bcfb" @@ -760,11 +761,11 @@ func TestDacPublisher(t *testing.T) { encryptedRef = "a5df670544eaea29e61b19d8739faa4573b19e4426e58a173e51ed0b5e7e2ade" ) client, _, _, _ := newTestServer(t, testServerOptions{ - Storer: storerMock, - Logger: logger, - Post: mockpost.New(mockpost.WithAcceptAll()), - PublicKey: pk.PublicKey, - Dac: mockdac.New(mockdac.WithHistory(h, fixtureHref.String()), mockdac.WithPublisher(publisher)), + Storer: storerMock, + Logger: logger, + Post: mockpost.New(mockpost.WithAcceptAll()), + PublicKey: pk.PublicKey, + AccessControl: mockac.New(mockac.WithHistory(h, fixtureHref.String()), mockac.WithPublisher(publisher)), }) jsonhttptest.Request(t, client, http.MethodGet, fileDownloadResource(encryptedRef), http.StatusInternalServerError, @@ -782,11 +783,11 @@ func TestDacPublisher(t *testing.T) { t.Run("download-w/o-publisher", func(t *testing.T) { encryptedRef := "a5df670544eaea29e61b19d8739faa4573b19e4426e58a173e51ed0b5e7e2ade" client, _, _, _ := newTestServer(t, testServerOptions{ - Storer: storerMock, - Logger: logger, - Post: mockpost.New(mockpost.WithAcceptAll()), - PublicKey: pk.PublicKey, - Dac: mockdac.New(mockdac.WithHistory(h, fixtureHref.String()), mockdac.WithPublisher(publisher)), + Storer: storerMock, + Logger: logger, + Post: mockpost.New(mockpost.WithAcceptAll()), + PublicKey: pk.PublicKey, + AccessControl: mockac.New(mockac.WithHistory(h, fixtureHref.String()), mockac.WithPublisher(publisher)), }) jsonhttptest.Request(t, client, http.MethodGet, fileDownloadResource(encryptedRef), http.StatusNotFound, @@ -798,7 +799,7 @@ func TestDacPublisher(t *testing.T) { }) } -func TestDacGrantees(t *testing.T) { +func TestAccessLogicGrantees(t *testing.T) { t.Parallel() var ( spk, _ = hex.DecodeString("a786dd84b61485de12146fd9c4c02d87e8fd95f0542765cb7fc3d2e428c0bcfa") @@ -808,11 +809,11 @@ func TestDacGrantees(t *testing.T) { logger = log.Noop addr = swarm.RandAddress(t) client, _, _, _ = newTestServer(t, testServerOptions{ - Storer: storerMock, - Logger: logger, - Post: mockpost.New(mockpost.WithAcceptAll()), - PublicKey: pk.PublicKey, - Dac: mockdac.New(mockdac.WithHistory(h, fixtureHref.String())), + Storer: storerMock, + Logger: logger, + Post: mockpost.New(mockpost.WithAcceptAll()), + PublicKey: pk.PublicKey, + AccessControl: mockac.New(mockac.WithHistory(h, fixtureHref.String())), }) ) t.Run("get-grantees", func(t *testing.T) { @@ -821,11 +822,11 @@ func TestDacGrantees(t *testing.T) { publisher = hex.EncodeToString(publicKeyBytes) ) clientwihtpublisher, _, _, _ := newTestServer(t, testServerOptions{ - Storer: storerMock, - Logger: logger, - Post: mockpost.New(mockpost.WithAcceptAll()), - PublicKey: pk.PublicKey, - Dac: mockdac.New(mockdac.WithHistory(h, fixtureHref.String()), mockdac.WithPublisher(publisher)), + Storer: storerMock, + Logger: logger, + Post: mockpost.New(mockpost.WithAcceptAll()), + PublicKey: pk.PublicKey, + AccessControl: mockac.New(mockac.WithHistory(h, fixtureHref.String()), mockac.WithPublisher(publisher)), }) expected := []string{ "03d7660772cc3142f8a7a2dfac46ce34d12eac1718720cef0e3d94347902aa96a2", diff --git a/pkg/api/api.go b/pkg/api/api.go index ec5f4a508ad..0d97a2abf0b 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -27,10 +27,10 @@ import ( "unicode/utf8" "github.com/ethereum/go-ethereum/common" + "github.com/ethersphere/bee/v2/pkg/accesscontrol" "github.com/ethersphere/bee/v2/pkg/accounting" "github.com/ethersphere/bee/v2/pkg/auth" "github.com/ethersphere/bee/v2/pkg/crypto" - "github.com/ethersphere/bee/v2/pkg/dynamicaccess" "github.com/ethersphere/bee/v2/pkg/feeds" "github.com/ethersphere/bee/v2/pkg/file/pipeline" "github.com/ethersphere/bee/v2/pkg/file/pipeline/builder" @@ -154,7 +154,7 @@ type Service struct { feedFactory feeds.Factory signer crypto.Signer post postage.Service - dac dynamicaccess.Controller + accesscontrol accesscontrol.Controller postageContract postagecontract.Interface probe *Probe metricsRegistry *prometheus.Registry @@ -253,7 +253,7 @@ type ExtraOptions struct { Pss pss.Interface FeedFactory feeds.Factory Post postage.Service - Dac dynamicaccess.Controller + AccessControl accesscontrol.Controller PostageContract postagecontract.Interface Staking staking.Contract Steward steward.Interface @@ -337,7 +337,7 @@ func (s *Service) Configure(signer crypto.Signer, auth auth.Authenticator, trace s.pss = e.Pss s.feedFactory = e.FeedFactory s.post = e.Post - s.dac = e.Dac + s.accesscontrol = e.AccessControl s.postageContract = e.PostageContract s.steward = e.Steward s.stakingContract = e.Staking diff --git a/pkg/api/api_test.go b/pkg/api/api_test.go index 8a6781f7f98..2e8ccbf5512 100644 --- a/pkg/api/api_test.go +++ b/pkg/api/api_test.go @@ -23,13 +23,13 @@ import ( "time" "github.com/ethereum/go-ethereum/common" + "github.com/ethersphere/bee/v2/pkg/accesscontrol" + mockac "github.com/ethersphere/bee/v2/pkg/accesscontrol/mock" accountingmock "github.com/ethersphere/bee/v2/pkg/accounting/mock" "github.com/ethersphere/bee/v2/pkg/api" "github.com/ethersphere/bee/v2/pkg/auth" mockauth "github.com/ethersphere/bee/v2/pkg/auth/mock" "github.com/ethersphere/bee/v2/pkg/crypto" - "github.com/ethersphere/bee/v2/pkg/dynamicaccess" - mockdac "github.com/ethersphere/bee/v2/pkg/dynamicaccess/mock" "github.com/ethersphere/bee/v2/pkg/feeds" "github.com/ethersphere/bee/v2/pkg/file/pipeline" "github.com/ethersphere/bee/v2/pkg/file/pipeline/builder" @@ -104,7 +104,7 @@ type testServerOptions struct { PostageContract postagecontract.Interface StakingContract staking.Contract Post postage.Service - Dac dynamicaccess.Controller + AccessControl accesscontrol.Controller Steward steward.Interface WsHeaders http.Header Authenticator auth.Authenticator @@ -155,8 +155,8 @@ func newTestServer(t *testing.T, o testServerOptions) (*http.Client, *websocket. if o.Post == nil { o.Post = mockpost.New() } - if o.Dac == nil { - o.Dac = mockdac.New() + if o.AccessControl == nil { + o.AccessControl = mockac.New() } if o.BatchStore == nil { o.BatchStore = mockbatchstore.New(mockbatchstore.WithAcceptAllExistsFunc()) // default is with accept-all Exists() func @@ -204,7 +204,7 @@ func newTestServer(t *testing.T, o testServerOptions) (*http.Client, *websocket. Pss: o.Pss, FeedFactory: o.Feeds, Post: o.Post, - Dac: o.Dac, + AccessControl: o.AccessControl, PostageContract: o.PostageContract, Steward: o.Steward, SyncStatus: o.SyncStatus, diff --git a/pkg/api/bzz.go b/pkg/api/bzz.go index dc26b26667a..bed074ff3ed 100644 --- a/pkg/api/bzz.go +++ b/pkg/api/bzz.go @@ -264,9 +264,9 @@ func (s *Service) fileUploadHandler( } logger.Debug("store", "manifest_reference", manifestReference) - encryptedReference := manifestReference + reference := manifestReference if act { - encryptedReference, err = s.actEncryptionHandler(r.Context(), w, putter, manifestReference, historyAddress) + reference, err = s.actEncryptionHandler(r.Context(), w, putter, reference, historyAddress) if err != nil { jsonhttp.InternalServerError(w, errActUpload) return @@ -275,24 +275,24 @@ func (s *Service) fileUploadHandler( err = putter.Done(manifestReference) if err != nil { - logger.Debug("done split failed", "error", err) + logger.Debug("done split failed", "reference", manifestReference, "error", err) logger.Error(nil, "done split failed") jsonhttp.InternalServerError(w, "done split failed") ext.LogError(span, err, olog.String("action", "putter.Done")) return } span.LogFields(olog.Bool("success", true)) - span.SetTag("root_address", encryptedReference) + span.SetTag("root_address", reference) if tagID != 0 { w.Header().Set(SwarmTagHeader, fmt.Sprint(tagID)) span.SetTag("tagID", tagID) } - w.Header().Set(ETagHeader, fmt.Sprintf("%q", encryptedReference.String())) + w.Header().Set(ETagHeader, fmt.Sprintf("%q", reference.String())) w.Header().Set("Access-Control-Expose-Headers", SwarmTagHeader) jsonhttp.Created(w, bzzUploadResponse{ - Reference: encryptedReference, + Reference: reference, }) } diff --git a/pkg/api/chunk.go b/pkg/api/chunk.go index 21daa0d0f57..534e2924866 100644 --- a/pkg/api/chunk.go +++ b/pkg/api/chunk.go @@ -141,9 +141,9 @@ func (s *Service) chunkUploadHandler(w http.ResponseWriter, r *http.Request) { } } - encryptedReference := chunk.Address() + reference := chunk.Address() if headers.Act { - encryptedReference, err = s.actEncryptionHandler(r.Context(), w, putter, chunk.Address(), headers.HistoryAddress) + reference, err = s.actEncryptionHandler(r.Context(), w, putter, reference, headers.HistoryAddress) if err != nil { jsonhttp.InternalServerError(w, errActUpload) return @@ -176,7 +176,7 @@ func (s *Service) chunkUploadHandler(w http.ResponseWriter, r *http.Request) { } w.Header().Set("Access-Control-Expose-Headers", SwarmTagHeader) - jsonhttp.Created(w, chunkAddressResponse{Reference: encryptedReference}) + jsonhttp.Created(w, chunkAddressResponse{Reference: reference}) } func (s *Service) chunkGetHandler(w http.ResponseWriter, r *http.Request) { diff --git a/pkg/api/soc.go b/pkg/api/soc.go index 29777066b10..3ceb462d028 100644 --- a/pkg/api/soc.go +++ b/pkg/api/soc.go @@ -157,9 +157,9 @@ func (s *Service) socUploadHandler(w http.ResponseWriter, r *http.Request) { return } - encryptedReference := sch.Address() + reference := sch.Address() if headers.Act { - encryptedReference, err = s.actEncryptionHandler(r.Context(), w, putter, sch.Address(), headers.HistoryAddress) + reference, err = s.actEncryptionHandler(r.Context(), w, putter, reference, headers.HistoryAddress) if err != nil { jsonhttp.InternalServerError(w, errActUpload) return @@ -182,5 +182,5 @@ func (s *Service) socUploadHandler(w http.ResponseWriter, r *http.Request) { return } - jsonhttp.Created(w, socPostResponse{Reference: encryptedReference}) + jsonhttp.Created(w, socPostResponse{Reference: reference}) } diff --git a/pkg/dynamicaccess/mock/accesslogic.go b/pkg/dynamicaccess/mock/accesslogic.go deleted file mode 100644 index 6b48c824eb9..00000000000 --- a/pkg/dynamicaccess/mock/accesslogic.go +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2024 The Swarm Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. -package mock - -type AccessLogicMock struct { - GetFunc func(string, string, string) (string, error) -} - -func (ma *AccessLogicMock) Get(encryped_ref string, publisher string, tag string) (string, error) { - if ma.GetFunc == nil { - return "", nil - } - return ma.GetFunc(encryped_ref, publisher, tag) -} diff --git a/pkg/node/devnode.go b/pkg/node/devnode.go index 389dc2337a5..853eff94807 100644 --- a/pkg/node/devnode.go +++ b/pkg/node/devnode.go @@ -17,12 +17,12 @@ import ( "time" "github.com/ethereum/go-ethereum/common" + "github.com/ethersphere/bee/v2/pkg/accesscontrol" mockAccounting "github.com/ethersphere/bee/v2/pkg/accounting/mock" "github.com/ethersphere/bee/v2/pkg/api" "github.com/ethersphere/bee/v2/pkg/auth" "github.com/ethersphere/bee/v2/pkg/bzz" "github.com/ethersphere/bee/v2/pkg/crypto" - "github.com/ethersphere/bee/v2/pkg/dynamicaccess" "github.com/ethersphere/bee/v2/pkg/feeds/factory" "github.com/ethersphere/bee/v2/pkg/log" mockP2P "github.com/ethersphere/bee/v2/pkg/p2p/mock" @@ -62,15 +62,15 @@ import ( ) type DevBee struct { - tracerCloser io.Closer - stateStoreCloser io.Closer - localstoreCloser io.Closer - apiCloser io.Closer - pssCloser io.Closer - dacCloser io.Closer - errorLogWriter io.Writer - apiServer *http.Server - debugAPIServer *http.Server + tracerCloser io.Closer + stateStoreCloser io.Closer + localstoreCloser io.Closer + apiCloser io.Closer + pssCloser io.Closer + accesscontrolCloser io.Closer + errorLogWriter io.Writer + apiServer *http.Server + debugAPIServer *http.Server } type DevOptions struct { @@ -236,10 +236,10 @@ func NewDevBee(logger log.Logger, o *DevOptions) (b *DevBee, err error) { } b.localstoreCloser = localStore - session := dynamicaccess.NewDefaultSession(mockKey) - actLogic := dynamicaccess.NewLogic(session) - dac := dynamicaccess.NewController(actLogic) - b.dacCloser = dac + session := accesscontrol.NewDefaultSession(mockKey) + actLogic := accesscontrol.NewLogic(session) + accesscontrol := accesscontrol.NewController(actLogic) + b.accesscontrolCloser = accesscontrol pssService := pss.New(mockKey, logger) b.pssCloser = pssService @@ -390,7 +390,7 @@ func NewDevBee(logger log.Logger, o *DevOptions) (b *DevBee, err error) { Pss: pssService, FeedFactory: mockFeeds, Post: post, - Dac: dac, + AccessControl: accesscontrol, PostageContract: postageContract, Staking: mockStaking, Steward: mockSteward, @@ -499,7 +499,7 @@ func (b *DevBee) Shutdown() error { } tryClose(b.pssCloser, "pss") - tryClose(b.dacCloser, "dac") + tryClose(b.accesscontrolCloser, "accesscontrol") tryClose(b.tracerCloser, "tracer") tryClose(b.stateStoreCloser, "statestore") tryClose(b.localstoreCloser, "localstore") diff --git a/pkg/node/node.go b/pkg/node/node.go index 4ab881bc8ab..09fa43b4589 100644 --- a/pkg/node/node.go +++ b/pkg/node/node.go @@ -24,13 +24,13 @@ import ( "time" "github.com/ethereum/go-ethereum/common" + "github.com/ethersphere/bee/v2/pkg/accesscontrol" "github.com/ethersphere/bee/v2/pkg/accounting" "github.com/ethersphere/bee/v2/pkg/addressbook" "github.com/ethersphere/bee/v2/pkg/api" "github.com/ethersphere/bee/v2/pkg/auth" "github.com/ethersphere/bee/v2/pkg/config" "github.com/ethersphere/bee/v2/pkg/crypto" - "github.com/ethersphere/bee/v2/pkg/dynamicaccess" "github.com/ethersphere/bee/v2/pkg/feeds/factory" "github.com/ethersphere/bee/v2/pkg/hive" "github.com/ethersphere/bee/v2/pkg/log" @@ -118,7 +118,7 @@ type Bee struct { shutdownInProgress bool shutdownMutex sync.Mutex syncingStopped *syncutil.Signaler - dacCloser io.Closer + accesscontrolCloser io.Closer } type Options struct { @@ -204,7 +204,7 @@ func NewBee( logger log.Logger, libp2pPrivateKey, pssPrivateKey *ecdsa.PrivateKey, - session dynamicaccess.Session, + session accesscontrol.Session, o *Options, ) (b *Bee, err error) { tracer, tracerCloser, err := tracing.NewTracer(&tracing.Options{ @@ -781,9 +781,9 @@ func NewBee( b.localstoreCloser = localStore evictFn = func(id []byte) error { return localStore.EvictBatch(context.Background(), id) } - actLogic := dynamicaccess.NewLogic(session) - dac := dynamicaccess.NewController(actLogic) - b.dacCloser = dac + actLogic := accesscontrol.NewLogic(session) + accesscontrol := accesscontrol.NewController(actLogic) + b.accesscontrolCloser = accesscontrol var ( syncErr atomic.Value @@ -1100,7 +1100,7 @@ func NewBee( Pss: pssService, FeedFactory: feedFactory, Post: post, - Dac: dac, + AccessControl: accesscontrol, PostageContract: postageStampContractService, Staking: stakingContract, Steward: steward, @@ -1363,7 +1363,7 @@ func (b *Bee) Shutdown() error { c() } - tryClose(b.dacCloser, "dac") + tryClose(b.accesscontrolCloser, "accesscontrol") tryClose(b.tracerCloser, "tracer") tryClose(b.topologyCloser, "topology driver") tryClose(b.storageIncetivesCloser, "storage incentives agent") diff --git a/pkg/soc/testing/soc.go b/pkg/soc/testing/soc.go index 9516a721d83..20bfe74c9cd 100644 --- a/pkg/soc/testing/soc.go +++ b/pkg/soc/testing/soc.go @@ -72,7 +72,7 @@ func GenerateMockSOC(t *testing.T, data []byte) *MockSOC { } } -// GenerateMockSOC generates a valid mocked SOC from given data and key. +// GenerateMockSOCWithKey generates a valid mocked SOC from given data and key. func GenerateMockSOCWithKey(t *testing.T, data []byte, privKey *ecdsa.PrivateKey) *MockSOC { t.Helper()