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 00dd73fdd01..2c93a20353b 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 1b1e1c877cb..8f3794b6828 100644 --- a/pkg/api/router.go +++ b/pkg/api/router.go @@ -266,6 +266,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 e838f93a287..275140e10a5 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 786c9abe864..4002693ff2e 100644 --- a/pkg/node/node.go +++ b/pkg/node/node.go @@ -776,11 +776,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 (