diff --git a/cmd/neofs-rest-gw/integration_test.go b/cmd/neofs-rest-gw/integration_test.go index 83c971a..a58c062 100644 --- a/cmd/neofs-rest-gw/integration_test.go +++ b/cmd/neofs-rest-gw/integration_test.go @@ -25,16 +25,16 @@ import ( "github.com/nspcc-dev/neofs-rest-gw/gen/restapi/operations" "github.com/nspcc-dev/neofs-rest-gw/handlers" "github.com/nspcc-dev/neofs-rest-gw/internal/util" - walletconnect "github.com/nspcc-dev/neofs-rest-gw/internal/wallet-connect" "github.com/nspcc-dev/neofs-sdk-go/container" + "github.com/nspcc-dev/neofs-sdk-go/container/acl" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" + neofsecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa" "github.com/nspcc-dev/neofs-sdk-go/eacl" + "github.com/nspcc-dev/neofs-sdk-go/netmap" "github.com/nspcc-dev/neofs-sdk-go/object" - "github.com/nspcc-dev/neofs-sdk-go/object/address" oid "github.com/nspcc-dev/neofs-sdk-go/object/id" - "github.com/nspcc-dev/neofs-sdk-go/owner" - "github.com/nspcc-dev/neofs-sdk-go/policy" "github.com/nspcc-dev/neofs-sdk-go/pool" + "github.com/nspcc-dev/neofs-sdk-go/user" "github.com/spf13/viper" "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go" @@ -59,7 +59,7 @@ const ( XBearerOwnerID = "X-Bearer-Owner-Id" // tests configuration. - useWalletConnect = false + useWalletConnect = true useLocalEnvironment = false ) @@ -82,11 +82,7 @@ func runLocalTests(ctx context.Context, t *testing.T, key *keys.PrivateKey) { func runTestInContainer(rootCtx context.Context, t *testing.T, key *keys.PrivateKey) { aioImage := "nspccdev/neofs-aio-testcontainer:" versions := []string{ - //"0.24.0", - //"0.25.1", - //"0.26.1", - //"0.27.5", - "0.27.7", + "0.29.0", //"latest", } @@ -112,25 +108,28 @@ func runTests(ctx context.Context, t *testing.T, key *keys.PrivateKey, version s cancel := runServer(ctx, t, node) defer cancel() + var owner user.ID + user.IDFromKey(&owner, key.PrivateKey.PublicKey) + clientPool := getPool(ctx, t, key, node) - cnrID := createContainer(ctx, t, clientPool, containerName) + cnrID := createContainer(ctx, t, clientPool, owner, containerName) restrictByEACL(ctx, t, clientPool, cnrID) t.Run("rest auth several tokens "+version, func(t *testing.T) { authTokens(ctx, t) }) t.Run("rest check mix tokens up "+version, func(t *testing.T) { mixTokens(ctx, t, cnrID) }) t.Run("rest put object "+version, func(t *testing.T) { restObjectPut(ctx, t, clientPool, cnrID) }) - t.Run("rest get object "+version, func(t *testing.T) { restObjectGet(ctx, t, clientPool, cnrID) }) - t.Run("rest delete object "+version, func(t *testing.T) { restObjectDelete(ctx, t, clientPool, cnrID) }) - t.Run("rest search objects "+version, func(t *testing.T) { restObjectsSearch(ctx, t, clientPool, cnrID) }) + t.Run("rest get object "+version, func(t *testing.T) { restObjectGet(ctx, t, clientPool, &owner, cnrID) }) + t.Run("rest delete object "+version, func(t *testing.T) { restObjectDelete(ctx, t, clientPool, &owner, cnrID) }) + t.Run("rest search objects "+version, func(t *testing.T) { restObjectsSearch(ctx, t, clientPool, &owner, cnrID) }) t.Run("rest put container invalid "+version, func(t *testing.T) { restContainerPutInvalid(ctx, t) }) t.Run("rest put container "+version, func(t *testing.T) { restContainerPut(ctx, t, clientPool) }) - t.Run("rest get container "+version, func(t *testing.T) { restContainerGet(ctx, t, clientPool, cnrID) }) - t.Run("rest delete container "+version, func(t *testing.T) { restContainerDelete(ctx, t, clientPool) }) - t.Run("rest put container eacl "+version, func(t *testing.T) { restContainerEACLPut(ctx, t, clientPool) }) + t.Run("rest get container "+version, func(t *testing.T) { restContainerGet(ctx, t, owner, cnrID) }) + t.Run("rest delete container "+version, func(t *testing.T) { restContainerDelete(ctx, t, clientPool, owner) }) + t.Run("rest put container eacl "+version, func(t *testing.T) { restContainerEACLPut(ctx, t, clientPool, owner) }) t.Run("rest get container eacl "+version, func(t *testing.T) { restContainerEACLGet(ctx, t, clientPool, cnrID) }) - t.Run("rest list containers "+version, func(t *testing.T) { restContainerList(ctx, t, clientPool, cnrID) }) + t.Run("rest list containers "+version, func(t *testing.T) { restContainerList(ctx, t, clientPool, owner, cnrID) }) } func createDockerContainer(ctx context.Context, t *testing.T, image string) testcontainers.Container { @@ -270,7 +269,7 @@ func authTokens(ctx context.Context, t *testing.T) { makeAuthTokenRequest(ctx, t, bearers, httpClient) } -func mixTokens(ctx context.Context, t *testing.T, cnrID *cid.ID) { +func mixTokens(ctx context.Context, t *testing.T, cnrID cid.ID) { bearers := []*models.Bearer{ { Name: "all-object", @@ -332,8 +331,8 @@ func checkPutContainerWithError(t *testing.T, httpClient *http.Client, token *ha checkGWErrorResponse(t, httpClient, request) } -func checkDeleteContainerWithError(t *testing.T, httpClient *http.Client, cnrID *cid.ID, token *handlers.BearerToken) { - reqURL, err := url.Parse(testHost + "/v1/containers/" + cnrID.String()) +func checkDeleteContainerWithError(t *testing.T, httpClient *http.Client, cnrID cid.ID, token *handlers.BearerToken) { + reqURL, err := url.Parse(testHost + "/v1/containers/" + cnrID.EncodeToString()) require.NoError(t, err) request, err := http.NewRequest(http.MethodDelete, reqURL.String(), nil) require.NoError(t, err) @@ -342,20 +341,20 @@ func checkDeleteContainerWithError(t *testing.T, httpClient *http.Client, cnrID checkGWErrorResponse(t, httpClient, request) } -func checkSetEACLContainerWithError(t *testing.T, httpClient *http.Client, cnrID *cid.ID, token *handlers.BearerToken) { +func checkSetEACLContainerWithError(t *testing.T, httpClient *http.Client, cnrID cid.ID, token *handlers.BearerToken) { req := models.Eacl{Records: []*models.Record{}} body, err := json.Marshal(&req) require.NoError(t, err) - request, err := http.NewRequest(http.MethodPut, testHost+"/v1/containers/"+cnrID.String()+"/eacl", bytes.NewReader(body)) + request, err := http.NewRequest(http.MethodPut, testHost+"/v1/containers/"+cnrID.EncodeToString()+"/eacl", bytes.NewReader(body)) require.NoError(t, err) prepareCommonHeaders(request.Header, token) checkGWErrorResponse(t, httpClient, request) } -func checkPutObjectWithError(t *testing.T, httpClient *http.Client, cnrID *cid.ID, token *handlers.BearerToken) { +func checkPutObjectWithError(t *testing.T, httpClient *http.Client, cnrID cid.ID, token *handlers.BearerToken) { req := &models.ObjectUpload{ - ContainerID: util.NewString(cnrID.String()), + ContainerID: util.NewString(cnrID.EncodeToString()), FileName: util.NewString("newFile.txt"), Payload: base64.StdEncoding.EncodeToString([]byte("content")), } @@ -377,7 +376,7 @@ func checkGWErrorResponse(t *testing.T, httpClient *http.Client, request *http.R require.Equal(t, models.ErrorTypeGW, *resp.Type) } -func restObjectPut(ctx context.Context, t *testing.T, clientPool *pool.Pool, cnrID *cid.ID) { +func restObjectPut(ctx context.Context, t *testing.T, clientPool *pool.Pool, cnrID cid.ID) { bearer := &models.Bearer{ Object: []*models.Record{{ Operation: models.NewOperation(models.OperationPUT), @@ -404,7 +403,7 @@ func restObjectPut(ctx context.Context, t *testing.T, clientPool *pool.Pool, cnr } req := &models.ObjectUpload{ - ContainerID: util.NewString(cnrID.String()), + ContainerID: util.NewString(cnrID.EncodeToString()), FileName: util.NewString("newFile.txt"), Payload: base64.StdEncoding.EncodeToString([]byte(content)), Attributes: []*models.Attribute{{ @@ -427,17 +426,17 @@ func restObjectPut(ctx context.Context, t *testing.T, clientPool *pool.Pool, cnr doRequest(t, httpClient, request, http.StatusOK, addr) var CID cid.ID - err = CID.Parse(*addr.ContainerID) + err = CID.DecodeString(*addr.ContainerID) require.NoError(t, err) var id oid.ID - err = id.Parse(*addr.ObjectID) + err = id.DecodeString(*addr.ObjectID) require.NoError(t, err) - objectAddress := address.NewAddress() - objectAddress.SetContainerID(&CID) - objectAddress.SetObjectID(&id) + var objectAddress oid.Address + objectAddress.SetContainer(CID) + objectAddress.SetObject(id) var prm pool.PrmObjectGet - prm.SetAddress(*objectAddress) + prm.SetAddress(objectAddress) res, err := clientPool.GetObject(ctx, prm) require.NoError(t, err) @@ -451,14 +450,14 @@ func restObjectPut(ctx context.Context, t *testing.T, clientPool *pool.Pool, cnr } } -func restObjectGet(ctx context.Context, t *testing.T, p *pool.Pool, cnrID *cid.ID) { +func restObjectGet(ctx context.Context, t *testing.T, p *pool.Pool, ownerID *user.ID, cnrID cid.ID) { content := []byte("some content") attributes := map[string]string{ object.AttributeFileName: "get-obj-name", "user-attribute": "user value", } - objID := createObject(ctx, t, p, cnrID, attributes, content) + objID := createObject(ctx, t, p, ownerID, cnrID, attributes, content) bearer := &models.Bearer{ Object: []*models.Record{ @@ -491,16 +490,16 @@ func restObjectGet(ctx context.Context, t *testing.T, p *pool.Pool, cnrID *cid.I query := make(url.Values) query.Add(walletConnectQuery, strconv.FormatBool(useWalletConnect)) - request, err := http.NewRequest(http.MethodGet, testHost+"/v1/objects/"+cnrID.String()+"/"+objID.String()+"?"+query.Encode(), nil) + request, err := http.NewRequest(http.MethodGet, testHost+"/v1/objects/"+cnrID.EncodeToString()+"/"+objID.EncodeToString()+"?"+query.Encode(), nil) require.NoError(t, err) prepareCommonHeaders(request.Header, bearerToken) objInfo := &models.ObjectInfo{} doRequest(t, httpClient, request, http.StatusOK, objInfo) - require.Equal(t, cnrID.String(), *objInfo.ContainerID) - require.Equal(t, objID.String(), *objInfo.ObjectID) - require.Equal(t, p.OwnerID().String(), *objInfo.OwnerID) + require.Equal(t, cnrID.EncodeToString(), *objInfo.ContainerID) + require.Equal(t, objID.EncodeToString(), *objInfo.ObjectID) + require.Equal(t, ownerID.EncodeToString(), *objInfo.OwnerID) require.Equal(t, len(attributes), len(objInfo.Attributes)) require.Equal(t, int64(len(content)), *objInfo.ObjectSize) @@ -517,7 +516,7 @@ func restObjectGet(ctx context.Context, t *testing.T, p *pool.Pool, cnrID *cid.I query.Add(walletConnectQuery, strconv.FormatBool(useWalletConnect)) query.Add("max-payload-size", "0") - request, err = http.NewRequest(http.MethodGet, testHost+"/v1/objects/"+cnrID.String()+"/"+objID.String()+"?"+query.Encode(), nil) + request, err = http.NewRequest(http.MethodGet, testHost+"/v1/objects/"+cnrID.EncodeToString()+"/"+objID.EncodeToString()+"?"+query.Encode(), nil) require.NoError(t, err) prepareCommonHeaders(request.Header, bearerToken) @@ -533,7 +532,7 @@ func restObjectGet(ctx context.Context, t *testing.T, p *pool.Pool, cnrID *cid.I query.Add("range-offset", "0") query.Add("range-length", strconv.Itoa(rangeLength)) - request, err = http.NewRequest(http.MethodGet, testHost+"/v1/objects/"+cnrID.String()+"/"+objID.String()+"?"+query.Encode(), nil) + request, err = http.NewRequest(http.MethodGet, testHost+"/v1/objects/"+cnrID.EncodeToString()+"/"+objID.EncodeToString()+"?"+query.Encode(), nil) require.NoError(t, err) prepareCommonHeaders(request.Header, bearerToken) @@ -546,8 +545,8 @@ func restObjectGet(ctx context.Context, t *testing.T, p *pool.Pool, cnrID *cid.I require.Equal(t, content[:rangeLength], contentData) } -func restObjectDelete(ctx context.Context, t *testing.T, p *pool.Pool, cnrID *cid.ID) { - objID := createObject(ctx, t, p, cnrID, nil, []byte("some content")) +func restObjectDelete(ctx context.Context, t *testing.T, p *pool.Pool, owner *user.ID, cnrID cid.ID) { + objID := createObject(ctx, t, p, owner, cnrID, nil, []byte("some content")) bearer := &models.Bearer{ Object: []*models.Record{{ @@ -569,7 +568,7 @@ func restObjectDelete(ctx context.Context, t *testing.T, p *pool.Pool, cnrID *ci query := make(url.Values) query.Add(walletConnectQuery, strconv.FormatBool(useWalletConnect)) - request, err := http.NewRequest(http.MethodDelete, testHost+"/v1/objects/"+cnrID.String()+"/"+objID.String()+"?"+query.Encode(), nil) + request, err := http.NewRequest(http.MethodDelete, testHost+"/v1/objects/"+cnrID.EncodeToString()+"/"+objID.EncodeToString()+"?"+query.Encode(), nil) require.NoError(t, err) prepareCommonHeaders(request.Header, bearerToken) @@ -577,9 +576,9 @@ func restObjectDelete(ctx context.Context, t *testing.T, p *pool.Pool, cnrID *ci doRequest(t, httpClient, request, http.StatusOK, resp) require.True(t, *resp.Success) - var addr address.Address - addr.SetContainerID(cnrID) - addr.SetObjectID(objID) + var addr oid.Address + addr.SetContainer(cnrID) + addr.SetObject(objID) var prm pool.PrmObjectHead prm.SetAddress(addr) @@ -588,16 +587,16 @@ func restObjectDelete(ctx context.Context, t *testing.T, p *pool.Pool, cnrID *ci require.Error(t, err) } -func restObjectsSearch(ctx context.Context, t *testing.T, p *pool.Pool, cnrID *cid.ID) { +func restObjectsSearch(ctx context.Context, t *testing.T, p *pool.Pool, owner *user.ID, cnrID cid.ID) { userKey, userValue := "User-Attribute", "user-attribute-value" objectName := "object-name" headers := map[string]string{ object.AttributeFileName: objectName, userKey: userValue, } - objID := createObject(ctx, t, p, cnrID, headers, []byte("some content")) + objID := createObject(ctx, t, p, owner, cnrID, headers, []byte("some content")) headers[userKey] = "dummy" - _ = createObject(ctx, t, p, cnrID, headers, []byte("some content")) + _ = createObject(ctx, t, p, owner, cnrID, headers, []byte("some content")) bearer := &models.Bearer{ Object: []*models.Record{ @@ -643,7 +642,7 @@ func restObjectsSearch(ctx context.Context, t *testing.T, p *pool.Pool, cnrID *c query := make(url.Values) query.Add(walletConnectQuery, strconv.FormatBool(useWalletConnect)) - request, err := http.NewRequest(http.MethodPost, testHost+"/v1/objects/"+cnrID.String()+"/search?"+query.Encode(), bytes.NewReader(body)) + request, err := http.NewRequest(http.MethodPost, testHost+"/v1/objects/"+cnrID.EncodeToString()+"/search?"+query.Encode(), bytes.NewReader(body)) require.NoError(t, err) prepareCommonHeaders(request.Header, bearerToken) @@ -654,8 +653,8 @@ func restObjectsSearch(ctx context.Context, t *testing.T, p *pool.Pool, cnrID *c require.Len(t, resp.Objects, 1) objBaseInfo := resp.Objects[0] - require.Equal(t, cnrID.String(), *objBaseInfo.Address.ContainerID) - require.Equal(t, objID.String(), *objBaseInfo.Address.ObjectID) + require.Equal(t, cnrID.EncodeToString(), *objBaseInfo.Address.ContainerID) + require.Equal(t, objID.EncodeToString(), *objBaseInfo.Address.ObjectID) require.Equal(t, objectName, objBaseInfo.Name) } @@ -682,21 +681,21 @@ func doRequest(t *testing.T, httpClient *http.Client, request *http.Request, exp require.NoError(t, err) } -func restContainerGet(ctx context.Context, t *testing.T, clientPool *pool.Pool, cnrID *cid.ID) { +func restContainerGet(ctx context.Context, t *testing.T, owner user.ID, cnrID cid.ID) { httpClient := &http.Client{Timeout: 5 * time.Second} - request, err := http.NewRequest(http.MethodGet, testHost+"/v1/containers/"+cnrID.String(), nil) + request, err := http.NewRequest(http.MethodGet, testHost+"/v1/containers/"+cnrID.EncodeToString(), nil) require.NoError(t, err) request = request.WithContext(ctx) cnrInfo := &models.ContainerInfo{} doRequest(t, httpClient, request, http.StatusOK, cnrInfo) - require.Equal(t, cnrID.String(), *cnrInfo.ContainerID) - require.Equal(t, clientPool.OwnerID().String(), *cnrInfo.OwnerID) + require.Equal(t, cnrID.EncodeToString(), *cnrInfo.ContainerID) + require.Equal(t, owner.EncodeToString(), *cnrInfo.OwnerID) } -func restContainerDelete(ctx context.Context, t *testing.T, clientPool *pool.Pool) { - cnrID := createContainer(ctx, t, clientPool, "for-delete") +func restContainerDelete(ctx context.Context, t *testing.T, clientPool *pool.Pool, owner user.ID) { + cnrID := createContainer(ctx, t, clientPool, owner, "for-delete") bearer := &models.Bearer{ Container: &models.Rule{ @@ -711,7 +710,7 @@ func restContainerDelete(ctx context.Context, t *testing.T, clientPool *pool.Poo query := make(url.Values) query.Add(walletConnectQuery, strconv.FormatBool(useWalletConnect)) - request, err := http.NewRequest(http.MethodDelete, testHost+"/v1/containers/"+cnrID.String()+"?"+query.Encode(), nil) + request, err := http.NewRequest(http.MethodDelete, testHost+"/v1/containers/"+cnrID.EncodeToString()+"?"+query.Encode(), nil) require.NoError(t, err) request = request.WithContext(ctx) prepareCommonHeaders(request.Header, bearerToken) @@ -721,15 +720,15 @@ func restContainerDelete(ctx context.Context, t *testing.T, clientPool *pool.Poo require.True(t, *resp.Success) var prm pool.PrmContainerGet - prm.SetContainerID(*cnrID) + prm.SetContainerID(cnrID) _, err = clientPool.GetContainer(ctx, prm) require.Error(t, err) require.Contains(t, err.Error(), "not found") } -func restContainerEACLPut(ctx context.Context, t *testing.T, clientPool *pool.Pool) { - cnrID := createContainer(ctx, t, clientPool, "for-eacl-put") +func restContainerEACLPut(ctx context.Context, t *testing.T, clientPool *pool.Pool, owner user.ID) { + cnrID := createContainer(ctx, t, clientPool, owner, "for-eacl-put") httpClient := &http.Client{Timeout: 60 * time.Second} bearer := &models.Bearer{ Container: &models.Rule{ @@ -757,7 +756,7 @@ func restContainerEACLPut(ctx context.Context, t *testing.T, clientPool *pool.Po query := make(url.Values) query.Add(walletConnectQuery, strconv.FormatBool(useWalletConnect)) - request, err := http.NewRequest(http.MethodPut, testHost+"/v1/containers/"+cnrID.String()+"/eacl?"+query.Encode(), bytes.NewReader(body)) + request, err := http.NewRequest(http.MethodPut, testHost+"/v1/containers/"+cnrID.EncodeToString()+"/eacl?"+query.Encode(), bytes.NewReader(body)) require.NoError(t, err) request = request.WithContext(ctx) prepareCommonHeaders(request.Header, bearerToken) @@ -767,7 +766,7 @@ func restContainerEACLPut(ctx context.Context, t *testing.T, clientPool *pool.Po require.True(t, *resp.Success) var prm pool.PrmContainerEACL - prm.SetContainerID(*cnrID) + prm.SetContainerID(cnrID) table, err := clientPool.GetEACL(ctx, prm) require.NoError(t, err) @@ -779,22 +778,22 @@ func restContainerEACLPut(ctx context.Context, t *testing.T, clientPool *pool.Po require.True(t, eacl.EqualTables(*expectedTable, *table)) } -func restContainerEACLGet(ctx context.Context, t *testing.T, p *pool.Pool, cnrID *cid.ID) { +func restContainerEACLGet(ctx context.Context, t *testing.T, p *pool.Pool, cnrID cid.ID) { var prm pool.PrmContainerEACL - prm.SetContainerID(*cnrID) + prm.SetContainerID(cnrID) expectedTable, err := p.GetEACL(ctx, prm) require.NoError(t, err) httpClient := &http.Client{Timeout: 60 * time.Second} - request, err := http.NewRequest(http.MethodGet, testHost+"/v1/containers/"+cnrID.String()+"/eacl", nil) + request, err := http.NewRequest(http.MethodGet, testHost+"/v1/containers/"+cnrID.EncodeToString()+"/eacl", nil) require.NoError(t, err) request = request.WithContext(ctx) responseTable := &models.Eacl{} doRequest(t, httpClient, request, http.StatusOK, responseTable) - require.Equal(t, cnrID.String(), responseTable.ContainerID) + require.Equal(t, cnrID.EncodeToString(), responseTable.ContainerID) actualTable, err := util.ToNativeTable(responseTable.Records) require.NoError(t, err) @@ -803,9 +802,9 @@ func restContainerEACLGet(ctx context.Context, t *testing.T, p *pool.Pool, cnrID require.True(t, eacl.EqualTables(*expectedTable, *actualTable)) } -func restContainerList(ctx context.Context, t *testing.T, p *pool.Pool, cnrID *cid.ID) { +func restContainerList(ctx context.Context, t *testing.T, p *pool.Pool, owner user.ID, cnrID cid.ID) { var prm pool.PrmContainerList - prm.SetOwnerID(*p.OwnerID()) + prm.SetOwnerID(owner) ids, err := p.ListContainers(ctx, prm) require.NoError(t, err) @@ -813,7 +812,7 @@ func restContainerList(ctx context.Context, t *testing.T, p *pool.Pool, cnrID *c httpClient := defaultHTTPClient() query := make(url.Values) - query.Add("ownerId", p.OwnerID().String()) + query.Add("ownerId", owner.EncodeToString()) request, err := http.NewRequest(http.MethodGet, testHost+"/v1/containers?"+query.Encode(), nil) require.NoError(t, err) @@ -824,14 +823,14 @@ func restContainerList(ctx context.Context, t *testing.T, p *pool.Pool, cnrID *c require.Equal(t, len(ids), int(*list.Size)) - require.Truef(t, containsContainer(list.Containers, cnrID.String(), containerName), "list doesn't contain cnr '%s' with name '%s'", cnrID.String(), containerName) + require.Truef(t, containsContainer(list.Containers, cnrID.EncodeToString(), containerName), "list doesn't contain cnr '%s' with name '%s'", cnrID.EncodeToString(), containerName) } func containsContainer(containers []*models.ContainerInfo, cnrID, cnrName string) bool { for _, cnrInfo := range containers { if *cnrInfo.ContainerID == cnrID { for _, attr := range cnrInfo.Attributes { - if *attr.Key == container.AttributeName && *attr.Value == cnrName { + if *attr.Key == "Name" && *attr.Value == cnrName { return true } } @@ -848,7 +847,8 @@ func makeAuthTokenRequest(ctx context.Context, t *testing.T, bearers []*models.B key, err := keys.NewPrivateKeyFromHex(devenvPrivateKey) require.NoError(t, err) - ownerID := owner.NewIDFromPublicKey((*ecdsa.PublicKey)(key.PublicKey())) + var ownerID user.ID + user.IDFromKey(&ownerID, key.PrivateKey.PublicKey) data, err := json.Marshal(bearers) require.NoError(t, err) @@ -924,14 +924,13 @@ func signToken(t *testing.T, key *keys.PrivateKey, data []byte) *handlers.Bearer } func signTokenWalletConnect(t *testing.T, key *keys.PrivateKey, data []byte) *handlers.BearerToken { - b64Token := make([]byte, base64.StdEncoding.EncodedLen(len(data))) - base64.StdEncoding.Encode(b64Token, data) - sm, err := walletconnect.SignMessage(&key.PrivateKey, b64Token[:]) + signer := neofsecdsa.SignerWalletConnect(key.PrivateKey) + signature, err := signer.Sign(data) require.NoError(t, err) return &handlers.BearerToken{ - Token: string(b64Token), - Signature: hex.EncodeToString(append(sm.Data, sm.Salt...)), + Token: base64.StdEncoding.EncodeToString(data), + Signature: hex.EncodeToString(signature), Key: hex.EncodeToString(key.PublicKey().Bytes()), } } @@ -1018,7 +1017,7 @@ func restContainerPut(ctx context.Context, t *testing.T, clientPool *pool.Pool) doRequest(t, httpClient, request, http.StatusOK, addr) var CID cid.ID - err = CID.Parse(*addr.ContainerID) + err = CID.DecodeString(*addr.ContainerID) require.NoError(t, err) fmt.Println(CID.String()) @@ -1028,10 +1027,10 @@ func restContainerPut(ctx context.Context, t *testing.T, clientPool *pool.Pool) cnr, err := clientPool.GetContainer(ctx, prm) require.NoError(t, err) - cnrAttr := make(map[string]string, len(cnr.Attributes())) - for _, attribute := range cnr.Attributes() { - cnrAttr[attribute.Key()] = attribute.Value() - } + cnrAttr := make(map[string]string) + cnr.IterateAttributes(func(key, val string) { + cnrAttr[key] = val + }) for key, val := range userAttributes { require.Equal(t, val, cnrAttr[key]) @@ -1045,32 +1044,38 @@ func prepareCommonHeaders(header http.Header, bearerToken *handlers.BearerToken) header.Add(XBearerSignatureKey, bearerToken.Key) } -func createContainer(ctx context.Context, t *testing.T, clientPool *pool.Pool, name string) *cid.ID { - pp, err := policy.Parse("REP 1") +func createContainer(ctx context.Context, t *testing.T, clientPool *pool.Pool, owner user.ID, name string) cid.ID { + var policy netmap.PlacementPolicy + err := policy.DecodeString("REP 1") require.NoError(t, err) - cnr := container.New( - container.WithPolicy(pp), - container.WithCustomBasicACL(0x0FFFFFFF), - container.WithAttribute(container.AttributeName, name), - container.WithAttribute(container.AttributeTimestamp, strconv.FormatInt(time.Now().Unix(), 10))) - cnr.SetOwnerID(clientPool.OwnerID()) + var cnr container.Container + cnr.Init() + cnr.SetOwner(owner) + cnr.SetPlacementPolicy(policy) + cnr.SetBasicACL(acl.PublicRWExtended) + + container.SetName(&cnr, name) + container.SetCreationTime(&cnr, time.Now()) + + err = pool.SyncContainerWithNetwork(ctx, &cnr, clientPool) + require.NoError(t, err) var waitPrm pool.WaitParams waitPrm.SetPollInterval(3 * time.Second) waitPrm.SetTimeout(15 * time.Second) var prm pool.PrmContainerPut - prm.SetContainer(*cnr) + prm.SetContainer(cnr) prm.SetWaitParams(waitPrm) CID, err := clientPool.PutContainer(ctx, prm) require.NoError(t, err) - return CID + return *CID } -func createObject(ctx context.Context, t *testing.T, p *pool.Pool, cnrID *cid.ID, headers map[string]string, payload []byte) *oid.ID { +func createObject(ctx context.Context, t *testing.T, p *pool.Pool, ownerID *user.ID, cnrID cid.ID, headers map[string]string, payload []byte) oid.ID { attributes := make([]object.Attribute, 0, len(headers)) for key, val := range headers { @@ -1081,7 +1086,7 @@ func createObject(ctx context.Context, t *testing.T, p *pool.Pool, cnrID *cid.ID } obj := object.New() - obj.SetOwnerID(p.OwnerID()) + obj.SetOwnerID(ownerID) obj.SetContainerID(cnrID) obj.SetAttributes(attributes...) obj.SetPayload(payload) @@ -1092,10 +1097,10 @@ func createObject(ctx context.Context, t *testing.T, p *pool.Pool, cnrID *cid.ID objID, err := p.PutObject(ctx, prm) require.NoError(t, err) - return objID + return *objID } -func restrictByEACL(ctx context.Context, t *testing.T, clientPool *pool.Pool, cnrID *cid.ID) *eacl.Table { +func restrictByEACL(ctx context.Context, t *testing.T, clientPool *pool.Pool, cnrID cid.ID) *eacl.Table { table := eacl.NewTable() table.SetCID(cnrID) diff --git a/go.mod b/go.mod index e777c0c..16f49c6 100644 --- a/go.mod +++ b/go.mod @@ -12,8 +12,8 @@ require ( github.com/go-openapi/validate v0.21.0 github.com/google/uuid v1.3.0 github.com/nspcc-dev/neo-go v0.98.2 - github.com/nspcc-dev/neofs-api-go/v2 v2.12.1 - github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.3.0.20220412151250-3e75660802ae + github.com/nspcc-dev/neofs-api-go/v2 v2.13.0 + github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.5 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.7.0 github.com/testcontainers/testcontainers-go v0.13.0 @@ -64,8 +64,9 @@ require ( github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c // indirect github.com/mr-tron/base58 v1.2.0 // indirect github.com/nspcc-dev/hrw v1.0.9 // indirect - github.com/nspcc-dev/neofs-crypto v0.3.0 + github.com/nspcc-dev/neofs-crypto v0.3.0 // indirect github.com/nspcc-dev/rfc6979 v0.2.0 // indirect + github.com/nspcc-dev/tzhash v1.5.2 // indirect github.com/oklog/ulid v1.3.1 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.2 // indirect diff --git a/go.sum b/go.sum index cb09ac5..17bb7ff 100644 --- a/go.sum +++ b/go.sum @@ -742,19 +742,22 @@ github.com/nspcc-dev/neo-go v0.98.2/go.mod h1:KXKqJwfTyVJzDarSCDqFaKrVbg/qz0ZBk2 github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20220321113211-526c423a6152/go.mod h1:QBE0I30F2kOAISNpT5oks82yF4wkkUq3SCfI3Hqgx/Y= github.com/nspcc-dev/neofs-api-go/v2 v2.11.0-pre.0.20211201134523-3604d96f3fe1/go.mod h1:oS8dycEh8PPf2Jjp6+8dlwWyEv2Dy77h/XhhcdxYEFs= github.com/nspcc-dev/neofs-api-go/v2 v2.11.1/go.mod h1:oS8dycEh8PPf2Jjp6+8dlwWyEv2Dy77h/XhhcdxYEFs= -github.com/nspcc-dev/neofs-api-go/v2 v2.12.1 h1:PVU2rLlG9S0jDe5eKyaUs4nKo/la+mN5pvz32Gib3qM= -github.com/nspcc-dev/neofs-api-go/v2 v2.12.1/go.mod h1:73j09Xa7I2zQbM3HCvAHnDHPYiiWnEHa1d6Z6RDMBLU= +github.com/nspcc-dev/neofs-api-go/v2 v2.13.0 h1:7BcBiSjmWqJx0SPFlGRYt9ZFbMjucRIz9+Mv8UBLeq8= +github.com/nspcc-dev/neofs-api-go/v2 v2.13.0/go.mod h1:73j09Xa7I2zQbM3HCvAHnDHPYiiWnEHa1d6Z6RDMBLU= +github.com/nspcc-dev/neofs-contract v0.15.1/go.mod h1:kxO5ZTqdzFnRM5RMvM+Fhd+3GGrJo6AmG2ZyA9OCqqQ= github.com/nspcc-dev/neofs-crypto v0.2.0/go.mod h1:F/96fUzPM3wR+UGsPi3faVNmFlA9KAEAUQR7dMxZmNA= github.com/nspcc-dev/neofs-crypto v0.2.3/go.mod h1:8w16GEJbH6791ktVqHN9YRNH3s9BEEKYxGhlFnp0cDw= github.com/nspcc-dev/neofs-crypto v0.3.0 h1:zlr3pgoxuzrmGCxc5W8dGVfA9Rro8diFvVnBg0L4ifM= github.com/nspcc-dev/neofs-crypto v0.3.0/go.mod h1:8w16GEJbH6791ktVqHN9YRNH3s9BEEKYxGhlFnp0cDw= github.com/nspcc-dev/neofs-sdk-go v0.0.0-20211201182451-a5b61c4f6477/go.mod h1:dfMtQWmBHYpl9Dez23TGtIUKiFvCIxUZq/CkSIhEpz4= github.com/nspcc-dev/neofs-sdk-go v0.0.0-20220113123743-7f3162110659/go.mod h1:/jay1lr3w7NQd/VDBkEhkJmDmyPNsu4W+QV2obsUV40= -github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.3.0.20220412151250-3e75660802ae h1:xcoEwEwZXu784Re1PPE35vm1A4+sUCVgMuGFqQPnN1Q= -github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.3.0.20220412151250-3e75660802ae/go.mod h1:Hl7a1l0ntZ4b1ZABpGX6fuAuFS3c6+hyMCUNVvZv/w4= +github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.5 h1:PSUUUL0XloTQdAgaI0WIY54TsqQ0GpxehHC7FaTkHvI= +github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.5/go.mod h1:hP3HrbK8omERJZvwjsGZgvzsUDxsPDPemrHzgqfpADM= github.com/nspcc-dev/rfc6979 v0.1.0/go.mod h1:exhIh1PdpDC5vQmyEsGvc4YDM/lyQp/452QxGq/UEso= github.com/nspcc-dev/rfc6979 v0.2.0 h1:3e1WNxrN60/6N0DW7+UYisLeZJyfqZTNOjeV/toYvOE= github.com/nspcc-dev/rfc6979 v0.2.0/go.mod h1:exhIh1PdpDC5vQmyEsGvc4YDM/lyQp/452QxGq/UEso= +github.com/nspcc-dev/tzhash v1.5.2 h1:GuIQPOY2xpl5ZE1pbUbz+QdKXVOTyzbbxSVv0nBfa98= +github.com/nspcc-dev/tzhash v1.5.2/go.mod h1:gwAx6mcsbkfY+JVp+PovoP2Gvw6y57W8dj7zDHKOhzI= github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= @@ -1291,6 +1294,7 @@ golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220403205710-6acee93ad0eb h1:PVGECzEo9Y3uOidtkHGdd347NjLtITfJFO9BxFpmRoo= golang.org/x/sys v0.0.0-20220403205710-6acee93ad0eb/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= diff --git a/handlers/api.go b/handlers/api.go index 97cc798..f40879c 100644 --- a/handlers/api.go +++ b/handlers/api.go @@ -14,6 +14,7 @@ import ( "github.com/nspcc-dev/neofs-rest-gw/gen/restapi/operations" "github.com/nspcc-dev/neofs-rest-gw/internal/util" "github.com/nspcc-dev/neofs-sdk-go/pool" + "github.com/nspcc-dev/neofs-sdk-go/user" "go.uber.org/zap" ) @@ -22,6 +23,7 @@ type API struct { log *zap.Logger pool *pool.Pool key *keys.PrivateKey + owner *user.ID defaultTimestamp bool } @@ -59,10 +61,14 @@ const ( // New creates a new API using specified logger, connection pool and other parameters. func New(prm *PrmAPI) *API { + var owner user.ID + user.IDFromKey(&owner, prm.Key.PrivateKey.PublicKey) + return &API{ log: prm.Logger, pool: prm.Pool, key: prm.Key, + owner: &owner, defaultTimestamp: prm.DefaultTimestamp, } } diff --git a/handlers/auth.go b/handlers/auth.go index 09cbc32..dd09a41 100644 --- a/handlers/auth.go +++ b/handlers/auth.go @@ -8,11 +8,15 @@ import ( "github.com/go-openapi/runtime/middleware" "github.com/google/uuid" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/nspcc-dev/neofs-api-go/v2/acl" + "github.com/nspcc-dev/neofs-api-go/v2/refs" + sessionv2 "github.com/nspcc-dev/neofs-api-go/v2/session" "github.com/nspcc-dev/neofs-rest-gw/gen/models" "github.com/nspcc-dev/neofs-rest-gw/gen/restapi/operations" "github.com/nspcc-dev/neofs-rest-gw/internal/util" - "github.com/nspcc-dev/neofs-sdk-go/owner" + neofsecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa" "github.com/nspcc-dev/neofs-sdk-go/pool" + "github.com/nspcc-dev/neofs-sdk-go/user" ) const defaultTokenExpDuration = 100 // in epoch @@ -83,7 +87,7 @@ func (a *API) PostAuth(params operations.AuthParams) middleware.Responder { if isObject { prm := newObjectParams(commonPrm, token) - response[i], err = prepareObjectToken(ctx, prm, a.pool) + response[i], err = prepareObjectToken(ctx, prm, a.pool, *a.owner) } else { prm := newContainerParams(commonPrm, token) response[i], err = prepareContainerTokens(ctx, prm, a.pool, a.key.PublicKey()) @@ -96,23 +100,23 @@ func (a *API) PostAuth(params operations.AuthParams) middleware.Responder { return operations.NewAuthOK().WithPayload(response) } -func prepareObjectToken(ctx context.Context, params objectTokenParams, pool *pool.Pool) (*models.TokenResponse, error) { +func prepareObjectToken(ctx context.Context, params objectTokenParams, pool *pool.Pool, owner user.ID) (*models.TokenResponse, error) { btoken, err := util.ToNativeObjectToken(params.Records) if err != nil { return nil, fmt.Errorf("couldn't transform token to native: %w", err) } - btoken.SetOwner(pool.OwnerID()) + btoken.ForUser(owner) iat, exp, err := getTokenLifetime(ctx, pool, params.XBearerLifetime) if err != nil { return nil, fmt.Errorf("couldn't get lifetime: %w", err) } - btoken.SetLifetime(exp, 0, iat) + btoken.SetIat(iat) + btoken.SetExp(exp) - binaryBearer, err := btoken.ToV2().GetBody().StableMarshal(nil) - if err != nil { - return nil, fmt.Errorf("couldn't marshal bearer token: %w", err) - } + var v2token acl.BearerToken + btoken.WriteToV2(&v2token) + binaryBearer := v2token.GetBody().StableMarshal(nil) return &models.TokenResponse{ Name: params.Name, @@ -127,8 +131,8 @@ func prepareContainerTokens(ctx context.Context, params containerTokenParams, po return nil, fmt.Errorf("couldn't get lifetime: %w", err) } - var ownerID owner.ID - if err = ownerID.Parse(params.XBearerOwnerID); err != nil { + var ownerID user.ID + if err = ownerID.DecodeString(params.XBearerOwnerID); err != nil { return nil, fmt.Errorf("invalid bearer owner: %w", err) } @@ -137,22 +141,21 @@ func prepareContainerTokens(ctx context.Context, params containerTokenParams, po return nil, fmt.Errorf("couldn't transform rule to native session token: %w", err) } - uid, err := uuid.New().MarshalBinary() - if err != nil { - return nil, err - } - stoken.SetID(uid) - - stoken.SetOwnerID(&ownerID) - + stoken.SetID(uuid.New()) stoken.SetIat(iat) stoken.SetExp(exp) - stoken.SetSessionKey(key.Bytes()) - binaryToken, err := stoken.ToV2().GetBody().StableMarshal(nil) - if err != nil { - return nil, fmt.Errorf("couldn't marshal session token: %w", err) - } + authKey := neofsecdsa.PublicKey(*key) + stoken.SetAuthKey(&authKey) + + var v2token sessionv2.Token + stoken.WriteToV2(&v2token) + + var issuer refs.OwnerID + ownerID.WriteToV2(&issuer) + v2token.GetBody().SetOwnerID(&issuer) + + binaryToken := v2token.GetBody().StableMarshal(nil) return &models.TokenResponse{ Name: params.Name, diff --git a/handlers/auth_test.go b/handlers/auth_test.go index 8fe37da..ca7d14f 100644 --- a/handlers/auth_test.go +++ b/handlers/auth_test.go @@ -7,12 +7,14 @@ import ( "crypto/sha512" "encoding/base64" "encoding/hex" + "math" "testing" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/nspcc-dev/neofs-api-go/v2/acl" "github.com/nspcc-dev/neofs-rest-gw/gen/models" "github.com/nspcc-dev/neofs-rest-gw/internal/util" - "github.com/nspcc-dev/neofs-sdk-go/owner" + "github.com/nspcc-dev/neofs-sdk-go/user" "github.com/stretchr/testify/require" ) @@ -37,14 +39,19 @@ func TestSign(t *testing.T) { btoken, err := util.ToNativeObjectToken(records) require.NoError(t, err) + btoken.SetExp(math.MaxInt64) + ownerKey, err := keys.NewPublicKeyFromString(pubKeyHex) require.NoError(t, err) - btoken.SetOwner(owner.NewIDFromPublicKey((*ecdsa.PublicKey)(ownerKey))) + var owner user.ID + user.IDFromKey(&owner, *(*ecdsa.PublicKey)(ownerKey)) + btoken.ForUser(owner) - binaryBearer, err := btoken.ToV2().GetBody().StableMarshal(nil) - require.NoError(t, err) + var v2token acl.BearerToken + btoken.WriteToV2(&v2token) + binaryBearer := v2token.GetBody().StableMarshal(nil) bearerBase64 := base64.StdEncoding.EncodeToString(binaryBearer) h := sha512.Sum512(binaryBearer) diff --git a/handlers/containers.go b/handlers/containers.go index 8d57a89..2824f4e 100644 --- a/handlers/containers.go +++ b/handlers/containers.go @@ -2,13 +2,11 @@ package handlers import ( "context" - "crypto/ecdsa" "encoding/base64" "encoding/hex" "errors" "fmt" "net/http" - "strconv" "strings" "time" @@ -19,20 +17,21 @@ import ( "github.com/nspcc-dev/neofs-rest-gw/gen/models" "github.com/nspcc-dev/neofs-rest-gw/gen/restapi/operations" "github.com/nspcc-dev/neofs-rest-gw/internal/util" - walletconnect "github.com/nspcc-dev/neofs-rest-gw/internal/wallet-connect" - "github.com/nspcc-dev/neofs-sdk-go/acl" "github.com/nspcc-dev/neofs-sdk-go/container" + "github.com/nspcc-dev/neofs-sdk-go/container/acl" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" - "github.com/nspcc-dev/neofs-sdk-go/owner" - "github.com/nspcc-dev/neofs-sdk-go/policy" + "github.com/nspcc-dev/neofs-sdk-go/netmap" "github.com/nspcc-dev/neofs-sdk-go/pool" "github.com/nspcc-dev/neofs-sdk-go/session" + "github.com/nspcc-dev/neofs-sdk-go/user" "go.uber.org/zap" ) const ( defaultPlacementPolicy = "REP 3" - defaultBasicACL = acl.PrivateBasicName + defaultBasicACL = acl.NamePrivate + attributeName = "Name" + attributeTimestamp = "Timestamp" ) // PutContainers handler that creates container in NeoFS. @@ -60,7 +59,7 @@ func (a *API) PutContainers(params operations.PutContainerParams, principal *mod } var resp operations.PutContainerOKBody - resp.ContainerID = util.NewString(cnrID.String()) + resp.ContainerID = util.NewString(cnrID.EncodeToString()) return operations.NewPutContainerOK().WithPayload(&resp) } @@ -73,7 +72,7 @@ func (a *API) GetContainer(params operations.GetContainerParams) middleware.Resp return operations.NewGetContainerBadRequest().WithPayload(resp) } - cnrInfo, err := getContainerInfo(params.HTTPRequest.Context(), a.pool, *cnrID) + cnrInfo, err := getContainerInfo(params.HTTPRequest.Context(), a.pool, cnrID) if err != nil { resp := a.logAndGetErrorResponse("get container", err) return operations.NewGetContainerBadRequest().WithPayload(resp) @@ -133,8 +132,8 @@ func (a *API) GetContainerEACL(params operations.GetContainerEACLParams) middlew func (a *API) ListContainer(params operations.ListContainersParams) middleware.Responder { ctx := params.HTTPRequest.Context() - var ownerID owner.ID - if err := ownerID.Parse(params.OwnerID); err != nil { + var ownerID user.ID + if err := ownerID.DecodeString(params.OwnerID); err != nil { resp := a.logAndGetErrorResponse("invalid owner id", err) return operations.NewListContainersBadRequest().WithPayload(resp) } @@ -203,8 +202,8 @@ func (a *API) DeleteContainer(params operations.DeleteContainerParams, principal } var prm pool.PrmContainerDelete - prm.SetContainerID(*cnrID) - prm.SetSessionToken(*stoken) + prm.SetContainerID(cnrID) + prm.SetSessionToken(stoken) if err = a.pool.DeleteContainer(params.HTTPRequest.Context(), prm); err != nil { resp := a.logAndGetErrorResponse("delete container", err, zap.String("container", params.ContainerID)) @@ -223,58 +222,62 @@ func getContainerInfo(ctx context.Context, p *pool.Pool, cnrID cid.ID) (*models. return nil, err } - attrs := make([]*models.Attribute, len(cnr.Attributes())) - for i, attr := range cnr.Attributes() { - attrs[i] = &models.Attribute{ - Key: util.NewString(attr.Key()), - Value: util.NewString(attr.Value()), - } + var attrs []*models.Attribute + cnr.IterateAttributes(func(key, val string) { + attrs = append(attrs, &models.Attribute{ + Key: util.NewString(key), + Value: util.NewString(val), + }) + }) + + var sb strings.Builder + if err = cnr.PlacementPolicy().WriteStringTo(&sb); err != nil { + return nil, fmt.Errorf("writer policy to string: %w", err) } return &models.ContainerInfo{ ContainerID: util.NewString(cnrID.String()), - Version: util.NewString(cnr.Version().String()), - OwnerID: util.NewString(cnr.OwnerID().String()), - BasicACL: util.NewString(acl.BasicACL(cnr.BasicACL()).String()), - PlacementPolicy: util.NewString(strings.Join(policy.Encode(cnr.PlacementPolicy()), " ")), + OwnerID: util.NewString(cnr.Owner().String()), + BasicACL: util.NewString(cnr.BasicACL().EncodeToString()), + PlacementPolicy: util.NewString(sb.String()), Attributes: attrs, }, nil } func prepareUserAttributes(header http.Header) map[string]string { filtered := filterHeaders(header) - delete(filtered, container.AttributeName) - delete(filtered, container.AttributeTimestamp) + delete(filtered, attributeName) + delete(filtered, attributeTimestamp) return filtered } -func parseContainerID(containerID string) (*cid.ID, error) { +func parseContainerID(containerID string) (cid.ID, error) { var cnrID cid.ID - if err := cnrID.Parse(containerID); err != nil { - return nil, fmt.Errorf("parse container id '%s': %w", containerID, err) + if err := cnrID.DecodeString(containerID); err != nil { + return cid.ID{}, fmt.Errorf("parse container id '%s': %w", containerID, err) } - return &cnrID, nil + return cnrID, nil } -func setContainerEACL(ctx context.Context, p *pool.Pool, cnrID *cid.ID, stoken *session.Token, eaclPrm *models.Eacl) error { +func setContainerEACL(ctx context.Context, p *pool.Pool, cnrID cid.ID, stoken session.Container, eaclPrm *models.Eacl) error { table, err := util.ToNativeTable(eaclPrm.Records) if err != nil { return err } table.SetCID(cnrID) - table.SetSessionToken(stoken) var prm pool.PrmContainerSetEACL prm.SetTable(*table) + prm.WithinSession(stoken) return p.SetEACL(ctx, prm) } -func getContainerEACL(ctx context.Context, p *pool.Pool, cnrID *cid.ID) (*models.Eacl, error) { +func getContainerEACL(ctx context.Context, p *pool.Pool, cnrID cid.ID) (*models.Eacl, error) { var prm pool.PrmContainerEACL - prm.SetContainerID(*cnrID) + prm.SetContainerID(cnrID) table, err := p.GetEACL(ctx, prm) if err != nil { @@ -282,7 +285,7 @@ func getContainerEACL(ctx context.Context, p *pool.Pool, cnrID *cid.ID) (*models } tableResp := &models.Eacl{ - ContainerID: cnrID.String(), + ContainerID: cnrID.EncodeToString(), Records: make([]*models.Record, len(table.Records())), } @@ -297,59 +300,63 @@ func getContainerEACL(ctx context.Context, p *pool.Pool, cnrID *cid.ID) (*models return tableResp, nil } -func createContainer(ctx context.Context, p *pool.Pool, stoken *session.Token, params *operations.PutContainerParams, userAttrs map[string]string) (*cid.ID, error) { +func createContainer(ctx context.Context, p *pool.Pool, stoken session.Container, params *operations.PutContainerParams, userAttrs map[string]string) (cid.ID, error) { request := params.Container if request.PlacementPolicy == "" { request.PlacementPolicy = defaultPlacementPolicy } - pp, err := policy.Parse(request.PlacementPolicy) + var policy netmap.PlacementPolicy + err := policy.DecodeString(request.PlacementPolicy) if err != nil { - return nil, fmt.Errorf("couldn't parse placement policy: %w", err) + return cid.ID{}, fmt.Errorf("couldn't parse placement policy: %w", err) } if request.BasicACL == "" { request.BasicACL = defaultBasicACL } - basicACL, err := acl.ParseBasicACL(request.BasicACL) - if err != nil { - return nil, fmt.Errorf("couldn't parse basic acl: %w", err) + + var basicACL acl.Basic + if err = basicACL.DecodeString(request.BasicACL); err != nil { + return cid.ID{}, fmt.Errorf("couldn't parse basic acl: %w", err) } - cnrOptions := []container.Option{ - container.WithPolicy(pp), - container.WithCustomBasicACL(basicACL), - container.WithAttribute(container.AttributeTimestamp, strconv.FormatInt(time.Now().Unix(), 10)), - } + var cnr container.Container + cnr.Init() + cnr.SetPlacementPolicy(policy) + cnr.SetBasicACL(basicACL) + cnr.SetOwner(stoken.Issuer()) + + container.SetCreationTime(&cnr, time.Now()) if request.ContainerName != "" { - container.WithAttribute(container.AttributeName, request.ContainerName) + container.SetName(&cnr, request.ContainerName) } for key, val := range userAttrs { - cnrOptions = append(cnrOptions, container.WithAttribute(key, val)) + cnr.SetAttribute(key, val) } - cnr := container.New(cnrOptions...) - cnr.SetOwnerID(stoken.OwnerID()) - cnr.SetSessionToken(stoken) - if *params.NameScopeGlobal { // we don't check for nil because there is default false value if err = checkNNSContainerName(request.ContainerName); err != nil { - return nil, fmt.Errorf("invalid container name: %w", err) + return cid.ID{}, fmt.Errorf("invalid container name: %w", err) } - container.SetNativeName(cnr, request.ContainerName) + + var domain container.Domain + domain.SetName(request.ContainerName) + container.WriteDomain(&cnr, domain) } var prm pool.PrmContainerPut - prm.SetContainer(*cnr) + prm.SetContainer(cnr) + prm.WithinSession(stoken) cnrID, err := p.PutContainer(ctx, prm) if err != nil { - return nil, fmt.Errorf("put container: %w", err) + return cid.ID{}, fmt.Errorf("put container: %w", err) } - return cnrID, nil + return *cnrID, nil } func checkNNSContainerName(name string) error { @@ -383,51 +390,52 @@ func isAlNum(c uint8) bool { return c >= 'a' && c <= 'z' || c >= '0' && c <= '9' } -func prepareSessionToken(st *SessionToken, isWalletConnect bool) (*session.Token, error) { +func prepareSessionToken(st *SessionToken, isWalletConnect bool) (session.Container, error) { data, err := base64.StdEncoding.DecodeString(st.Token) if err != nil { - return nil, fmt.Errorf("can't base64-decode session token: %w", err) + return session.Container{}, fmt.Errorf("can't base64-decode session token: %w", err) } signature, err := hex.DecodeString(st.Signature) if err != nil { - return nil, fmt.Errorf("couldn't decode signature: %w", err) + return session.Container{}, fmt.Errorf("couldn't decode signature: %w", err) } ownerKey, err := keys.NewPublicKeyFromString(st.Key) if err != nil { - return nil, fmt.Errorf("couldn't fetch session token owner key: %w", err) + return session.Container{}, fmt.Errorf("couldn't fetch session token owner key: %w", err) } body := new(sessionv2.TokenBody) if err = body.Unmarshal(data); err != nil { - return nil, fmt.Errorf("can't unmarshal session token: %w", err) + return session.Container{}, fmt.Errorf("can't unmarshal session token: %w", err) } if sessionContext, ok := body.GetContext().(*sessionv2.ContainerSessionContext); !ok { - return nil, errors.New("expected container session context but got something different") + return session.Container{}, errors.New("expected container session context but got something different") } else if sessionContext.Verb() != st.Verb { - return nil, fmt.Errorf("invalid container session verb '%s', expected: '%s'", sessionContext.Verb().String(), st.Verb.String()) + return session.Container{}, fmt.Errorf("invalid container session verb '%s', expected: '%s'", sessionContext.Verb().String(), st.Verb.String()) } - stoken := new(session.Token) - stoken.ToV2().SetBody(body) - v2signature := new(refs.Signature) v2signature.SetScheme(refs.ECDSA_SHA512) if isWalletConnect { - v2signature.SetScheme(2) + v2signature.SetScheme(refs.ECDSA_RFC6979_SHA256_WALLET_CONNECT) } v2signature.SetSign(signature) v2signature.SetKey(ownerKey.Bytes()) - stoken.ToV2().SetSignature(v2signature) - if isWalletConnect { - if !walletconnect.Verify((*ecdsa.PublicKey)(ownerKey), []byte(st.Token), signature) { - return nil, fmt.Errorf("invalid signature") - } - } else if !stoken.VerifySignature() { - return nil, fmt.Errorf("invalid signature") + var v2token sessionv2.Token + v2token.SetBody(body) + v2token.SetSignature(v2signature) + + var stoken session.Container + if err = stoken.ReadFromV2(v2token); err != nil { + return session.Container{}, fmt.Errorf("read from v2 token: %w", err) + } + + if !stoken.VerifySignature() { + return session.Container{}, fmt.Errorf("invalid signature") } return stoken, err diff --git a/handlers/objects.go b/handlers/objects.go index ebdae9b..8177e57 100644 --- a/handlers/objects.go +++ b/handlers/objects.go @@ -2,7 +2,6 @@ package handlers import ( "context" - "crypto/ecdsa" "encoding/base64" "encoding/hex" "errors" @@ -17,13 +16,11 @@ import ( "github.com/nspcc-dev/neofs-rest-gw/gen/models" "github.com/nspcc-dev/neofs-rest-gw/gen/restapi/operations" "github.com/nspcc-dev/neofs-rest-gw/internal/util" - walletconnect "github.com/nspcc-dev/neofs-rest-gw/internal/wallet-connect" + "github.com/nspcc-dev/neofs-sdk-go/bearer" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" "github.com/nspcc-dev/neofs-sdk-go/object" - "github.com/nspcc-dev/neofs-sdk-go/object/address" oid "github.com/nspcc-dev/neofs-sdk-go/object/id" "github.com/nspcc-dev/neofs-sdk-go/pool" - "github.com/nspcc-dev/neofs-sdk-go/token" "go.uber.org/zap" ) @@ -39,7 +36,7 @@ func (a *API) PutObjects(params operations.PutObjectParams, principal *models.Pr } var cnrID cid.ID - if err = cnrID.Parse(*params.Object.ContainerID); err != nil { + if err = cnrID.DecodeString(*params.Object.ContainerID); err != nil { resp := a.logAndGetErrorResponse("invalid container id", err) return errorResponse.WithPayload(resp) } @@ -60,9 +57,11 @@ func (a *API) PutObjects(params operations.PutObjectParams, principal *models.Pr return errorResponse.WithPayload(resp) } + owner := bearer.ResolveIssuer(btoken) + obj := object.New() - obj.SetContainerID(&cnrID) - obj.SetOwnerID(btoken.OwnerID()) + obj.SetContainerID(cnrID) + obj.SetOwnerID(&owner) obj.SetPayload(payload) obj.SetAttributes(attributes...) @@ -101,7 +100,7 @@ func (a *API) GetObjectInfo(params operations.GetObjectInfoParams, principal *mo } var prm pool.PrmObjectHead - prm.SetAddress(*addr) + prm.SetAddress(addr) prm.UseBearer(btoken) objInfo, err := a.pool.HeadObject(ctx, prm) @@ -126,7 +125,7 @@ func (a *API) GetObjectInfo(params operations.GetObjectInfoParams, principal *mo } var prmRange pool.PrmObjectRange - prmRange.SetAddress(*addr) + prmRange.SetAddress(addr) prmRange.UseBearer(btoken) var offset, length uint64 @@ -195,7 +194,7 @@ func (a *API) DeleteObject(params operations.DeleteObjectParams, principal *mode } var prm pool.PrmObjectDelete - prm.SetAddress(*addr) + prm.SetAddress(addr) prm.UseBearer(btoken) if err = a.pool.DeleteObject(ctx, prm); err != nil { @@ -212,7 +211,7 @@ func (a *API) SearchObjects(params operations.SearchObjectsParams, principal *mo ctx := params.HTTPRequest.Context() var cnrID cid.ID - if err := cnrID.Parse(params.ContainerID); err != nil { + if err := cnrID.DecodeString(params.ContainerID); err != nil { resp := a.logAndGetErrorResponse("invalid container id", err) return errorResponse.WithPayload(resp) } @@ -254,7 +253,7 @@ func (a *API) SearchObjects(params operations.SearchObjectsParams, principal *mo return false } - if obj, iterateErr = headObjectBaseInfo(ctx, a.pool, &cnrID, &id, btoken); iterateErr != nil { + if obj, iterateErr = headObjectBaseInfo(ctx, a.pool, cnrID, id, btoken); iterateErr != nil { return true } @@ -278,13 +277,13 @@ func (a *API) SearchObjects(params operations.SearchObjectsParams, principal *mo return operations.NewSearchObjectsOK().WithPayload(list) } -func headObjectBaseInfo(ctx context.Context, p *pool.Pool, cnrID *cid.ID, objID *oid.ID, btoken *token.BearerToken) (*models.ObjectBaseInfo, error) { - addr := address.NewAddress() - addr.SetContainerID(cnrID) - addr.SetObjectID(objID) +func headObjectBaseInfo(ctx context.Context, p *pool.Pool, cnrID cid.ID, objID oid.ID, btoken bearer.Token) (*models.ObjectBaseInfo, error) { + var addr oid.Address + addr.SetContainer(cnrID) + addr.SetObject(objID) var prm pool.PrmObjectHead - prm.SetAddress(*addr) + prm.SetAddress(addr) prm.UseBearer(btoken) objInfo, err := p.HeadObject(ctx, prm) @@ -309,24 +308,24 @@ func headObjectBaseInfo(ctx context.Context, p *pool.Pool, cnrID *cid.ID, objID return resp, nil } -func parseAddress(containerID, objectID string) (*address.Address, error) { +func parseAddress(containerID, objectID string) (oid.Address, error) { var cnrID cid.ID - if err := cnrID.Parse(containerID); err != nil { - return nil, fmt.Errorf("invalid container id: %w", err) + if err := cnrID.DecodeString(containerID); err != nil { + return oid.Address{}, fmt.Errorf("invalid container id: %w", err) } var objID oid.ID - if err := objID.Parse(objectID); err != nil { - return nil, fmt.Errorf("invalid object id: %w", err) + if err := objID.DecodeString(objectID); err != nil { + return oid.Address{}, fmt.Errorf("invalid object id: %w", err) } - addr := address.NewAddress() - addr.SetContainerID(&cnrID) - addr.SetObjectID(&objID) + var addr oid.Address + addr.SetContainer(cnrID) + addr.SetObject(objID) return addr, nil } -func getBearerToken(token *models.Principal, signature, key string, isWalletConnect bool) (*token.BearerToken, error) { +func getBearerToken(token *models.Principal, signature, key string, isWalletConnect bool) (bearer.Token, error) { bt := &BearerToken{ Token: string(*token), Signature: signature, @@ -336,45 +335,46 @@ func getBearerToken(token *models.Principal, signature, key string, isWalletConn return prepareBearerToken(bt, isWalletConnect) } -func prepareBearerToken(bt *BearerToken, isWalletConnect bool) (*token.BearerToken, error) { +func prepareBearerToken(bt *BearerToken, isWalletConnect bool) (bearer.Token, error) { data, err := base64.StdEncoding.DecodeString(bt.Token) if err != nil { - return nil, fmt.Errorf("can't base64-decode bearer token: %w", err) + return bearer.Token{}, fmt.Errorf("can't base64-decode bearer token: %w", err) } signature, err := hex.DecodeString(bt.Signature) if err != nil { - return nil, fmt.Errorf("couldn't decode bearer signature: %w", err) + return bearer.Token{}, fmt.Errorf("couldn't decode bearer signature: %w", err) } ownerKey, err := keys.NewPublicKeyFromString(bt.Key) if err != nil { - return nil, fmt.Errorf("couldn't fetch bearer token owner key: %w", err) + return bearer.Token{}, fmt.Errorf("couldn't fetch bearer token owner key: %w", err) } body := new(acl.BearerTokenBody) if err = body.Unmarshal(data); err != nil { - return nil, fmt.Errorf("can't unmarshal bearer token: %w", err) + return bearer.Token{}, fmt.Errorf("can't unmarshal bearer token: %w", err) } - btoken := new(token.BearerToken) - btoken.ToV2().SetBody(body) - v2signature := new(refs.Signature) v2signature.SetScheme(refs.ECDSA_SHA512) if isWalletConnect { - v2signature.SetScheme(2) + v2signature.SetScheme(refs.ECDSA_RFC6979_SHA256_WALLET_CONNECT) } v2signature.SetSign(signature) v2signature.SetKey(ownerKey.Bytes()) - btoken.ToV2().SetSignature(v2signature) - if isWalletConnect { - if !walletconnect.Verify((*ecdsa.PublicKey)(ownerKey), []byte(bt.Token), signature) { - return nil, fmt.Errorf("invalid signature") - } - } else if err = btoken.VerifySignature(); err != nil { - return nil, fmt.Errorf("invalid signature") + var v2btoken acl.BearerToken + v2btoken.SetBody(body) + v2btoken.SetSignature(v2signature) + + var btoken bearer.Token + if err = btoken.ReadFromV2(v2btoken); err != nil { + return bearer.Token{}, fmt.Errorf("read from v2 token: %w", err) + } + + if !btoken.VerifySignature() { + return bearer.Token{}, fmt.Errorf("invalid signature") } return btoken, nil diff --git a/handlers/util.go b/handlers/util.go index 1a6dc97..310df4e 100644 --- a/handlers/util.go +++ b/handlers/util.go @@ -2,7 +2,6 @@ package handlers import ( "context" - "encoding/binary" "fmt" "net/http" "strconv" @@ -11,7 +10,6 @@ import ( objectv2 "github.com/nspcc-dev/neofs-api-go/v2/object" "github.com/nspcc-dev/neofs-rest-gw/gen/models" - "github.com/nspcc-dev/neofs-sdk-go/netmap" "github.com/nspcc-dev/neofs-sdk-go/object" "github.com/nspcc-dev/neofs-sdk-go/pool" ) @@ -134,21 +132,13 @@ func getEpochDurations(ctx context.Context, p *pool.Pool) (*epochDurations, erro } res := &epochDurations{ - currentEpoch: networkInfo.CurrentEpoch(), - msPerBlock: networkInfo.MsPerBlock(), + currentEpoch: networkInfo.CurrentEpoch(), + msPerBlock: networkInfo.MsPerBlock(), + blockPerEpoch: networkInfo.EpochDuration(), } - networkInfo.NetworkConfig().IterateParameters(func(parameter *netmap.NetworkParameter) bool { - if string(parameter.Key()) == "EpochDuration" { - data := make([]byte, 8) - copy(data, parameter.Value()) - res.blockPerEpoch = binary.LittleEndian.Uint64(data) - return true - } - return false - }) if res.blockPerEpoch == 0 { - return nil, fmt.Errorf("not found param: EpochDuration") + return nil, fmt.Errorf("EpochDuration is zero") } return res, nil } diff --git a/internal/util/transformers.go b/internal/util/transformers.go index d2605b5..de2046f 100644 --- a/internal/util/transformers.go +++ b/internal/util/transformers.go @@ -5,14 +5,13 @@ import ( "errors" "fmt" - sessionv2 "github.com/nspcc-dev/neofs-api-go/v2/session" "github.com/nspcc-dev/neofs-rest-gw/gen/models" + "github.com/nspcc-dev/neofs-sdk-go/bearer" apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" "github.com/nspcc-dev/neofs-sdk-go/eacl" "github.com/nspcc-dev/neofs-sdk-go/object" "github.com/nspcc-dev/neofs-sdk-go/session" - "github.com/nspcc-dev/neofs-sdk-go/token" ) // ToNativeAction converts models.Action to appropriate eacl.Action. @@ -184,54 +183,40 @@ func FromNativeRole(r eacl.Role) (*models.Role, error) { } // ToNativeVerb converts models.Verb to appropriate session.ContainerSessionVerb. -func ToNativeVerb(r *models.Verb) (sessionv2.ContainerSessionVerb, error) { +func ToNativeVerb(r *models.Verb) (session.ContainerVerb, error) { if r == nil { - return sessionv2.ContainerVerbUnknown, fmt.Errorf("unsupported empty verb type") + return 0, fmt.Errorf("unsupported empty verb type") } switch *r { case models.VerbPUT: - return sessionv2.ContainerVerbPut, nil + return session.VerbContainerPut, nil case models.VerbDELETE: - return sessionv2.ContainerVerbDelete, nil + return session.VerbContainerDelete, nil case models.VerbSETEACL: - return sessionv2.ContainerVerbSetEACL, nil + return session.VerbContainerSetEACL, nil default: - return sessionv2.ContainerVerbUnknown, fmt.Errorf("unsupported verb type: '%s'", *r) + return 0, fmt.Errorf("unsupported verb type: '%s'", *r) } } -// ToNativeRule converts models.Rule to appropriate session.ContainerContext. -func ToNativeRule(r *models.Rule) (*session.ContainerContext, error) { - var ctx session.ContainerContext - - verb, err := ToNativeVerb(r.Verb) - if err != nil { - return nil, err - } - ctx.ToV2().SetVerb(verb) - - if r.ContainerID == "" { - ctx.ApplyTo(nil) - } else { - var cnrID cid.ID - if err = cnrID.Parse(r.ContainerID); err != nil { - return nil, fmt.Errorf("couldn't parse container id: %w", err) - } - ctx.ApplyTo(&cnrID) - } - - return &ctx, nil -} - // ToNativeContainerToken converts models.Rule to appropriate session.Token. -func ToNativeContainerToken(tokenRule *models.Rule) (*session.Token, error) { - sctx, err := ToNativeRule(tokenRule) - if err != nil { - return nil, fmt.Errorf("couldn't transform rule to native: %w", err) +func ToNativeContainerToken(tokenRule *models.Rule) (session.Container, error) { + var tok session.Container + + if tokenRule.ContainerID != "" { + var cnrID cid.ID + if err := cnrID.DecodeString(tokenRule.ContainerID); err != nil { + return session.Container{}, fmt.Errorf("couldn't parse container id: %w", err) + } + tok.ApplyOnlyTo(cnrID) } - tok := session.NewToken() - tok.SetContext(sctx) + + verb, err := ToNativeVerb(tokenRule.Verb) + if err != nil { + return session.Container{}, err + } + tok.ForVerb(verb) return tok, nil } @@ -368,14 +353,14 @@ func FromNativeTarget(t eacl.Target) (*models.Target, error) { } // ToNativeObjectToken converts []*models.Record to appropriate token.BearerToken. -func ToNativeObjectToken(tokenRecords []*models.Record) (*token.BearerToken, error) { +func ToNativeObjectToken(tokenRecords []*models.Record) (*bearer.Token, error) { table, err := ToNativeTable(tokenRecords) if err != nil { return nil, err } - var btoken token.BearerToken - btoken.SetEACLTable(table) + var btoken bearer.Token + btoken.SetEACLTable(*table) return &btoken, nil } diff --git a/internal/wallet-connect/wallet_connect.go b/internal/wallet-connect/wallet_connect.go deleted file mode 100644 index 8bff3f1..0000000 --- a/internal/wallet-connect/wallet_connect.go +++ /dev/null @@ -1,149 +0,0 @@ -package walletconnect - -import ( - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rand" - "encoding/binary" - "encoding/hex" - - crypto "github.com/nspcc-dev/neofs-crypto" -) - -const ( - // saltSize is the salt size added to signed message. - saltSize = 16 - // signatureLen is the length of RFC6979 signature. - signatureLen = 64 -) - -// SignedMessage contains mirrors `SignedMessage` struct from the WalletConnect API. -// https://neon.coz.io/wksdk/core/modules.html#SignedMessage -type SignedMessage struct { - Data []byte - Message []byte - PublicKey []byte - Salt []byte -} - -// Sign signs message using WalletConnect API. The returned signature -// contains RFC6979 signature and 16-byte salt. -func Sign(p *ecdsa.PrivateKey, msg []byte) ([]byte, error) { - sm, err := SignMessage(p, msg) - if err != nil { - return nil, err - } - return append(sm.Data, sm.Salt...), nil -} - -// Verify verifies message using WalletConnect API. The returned signature -// contains RFC6979 signature and 16-byte salt. -func Verify(p *ecdsa.PublicKey, data, sign []byte) bool { - if len(sign) != signatureLen+saltSize { - return false - } - - salt := sign[signatureLen:] - return VerifyMessage(p, SignedMessage{ - Data: sign[:signatureLen], - Message: createMessageWithSalt(data, salt), - Salt: salt, - }) -} - -// SignMessage signs message with a private key and returns structure similar to -// `signMessage` of the WalletConnect API. -// https://github.com/CityOfZion/wallet-connect-sdk/blob/89c236b/packages/wallet-connect-sdk-core/src/index.ts#L496 -// https://github.com/CityOfZion/neon-wallet/blob/1174a9388480e6bbc4f79eb13183c2a573f67ca8/app/context/WalletConnect/helpers.js#L133 -func SignMessage(p *ecdsa.PrivateKey, msg []byte) (SignedMessage, error) { - var salt [saltSize]byte - _, _ = rand.Read(salt[:]) - - msg = createMessageWithSalt(msg, salt[:]) - sign, err := crypto.SignRFC6979(p, msg) - if err != nil { - return SignedMessage{}, err - } - - return SignedMessage{ - Data: sign, - Message: msg, - PublicKey: elliptic.MarshalCompressed(p.Curve, p.X, p.Y), - Salt: salt[:], - }, nil -} - -// VerifyMessage verifies message with a private key and returns structure similar to -// `verifyMessage` of WalletConnect API. -// https://github.com/CityOfZion/wallet-connect-sdk/blob/89c236b/packages/wallet-connect-sdk-core/src/index.ts#L515 -// https://github.com/CityOfZion/neon-wallet/blob/1174a9388480e6bbc4f79eb13183c2a573f67ca8/app/context/WalletConnect/helpers.js#L147 -func VerifyMessage(p *ecdsa.PublicKey, m SignedMessage) bool { - if p == nil { - x, y := elliptic.UnmarshalCompressed(elliptic.P256(), m.PublicKey) - if x == nil || y == nil { - return false - } - p = &ecdsa.PublicKey{ - Curve: elliptic.P256(), - X: x, - Y: y, - } - } - return crypto.VerifyRFC6979(p, m.Message, m.Data) == nil -} - -func createMessageWithSalt(msg, salt []byte) []byte { - // 4 byte prefix + length of the message with salt in bytes + - // + salt + message + 2 byte postfix. - saltedLen := len(salt)*2 + len(msg) - data := make([]byte, 4+getVarIntSize(saltedLen)+saltedLen+2) - - n := copy(data, []byte{0x01, 0x00, 0x01, 0xf0}) // fixed prefix - n += putVarUint(data[n:], uint64(saltedLen)) - n += hex.Encode(data[n:], salt[:]) // for some reason we encode salt in hex - n += copy(data[n:], msg) - copy(data[n:], []byte{0x00, 0x00}) - - return data -} - -// Following functions are copied from github.com/nspcc-dev/neo-go/pkg/io package -// to avoid having another dependency. - -// getVarIntSize returns the size in number of bytes of a variable integer. -// (reference: GetVarSize(int value), https://github.com/neo-project/neo/blob/master/neo/IO/Helper.cs) -func getVarIntSize(value int) int { - var size uintptr - - if value < 0xFD { - size = 1 // unit8 - } else if value <= 0xFFFF { - size = 3 // byte + uint16 - } else { - size = 5 // byte + uint32 - } - return int(size) -} - -// putVarUint puts val in varint form to the pre-allocated buffer. -func putVarUint(data []byte, val uint64) int { - _ = data[8] - if val < 0xfd { - data[0] = byte(val) - return 1 - } - if val < 0xFFFF { - data[0] = byte(0xfd) - binary.LittleEndian.PutUint16(data[1:], uint16(val)) - return 3 - } - if val < 0xFFFFFFFF { - data[0] = byte(0xfe) - binary.LittleEndian.PutUint32(data[1:], uint32(val)) - return 5 - } - - data[0] = byte(0xff) - binary.LittleEndian.PutUint64(data[1:], val) - return 9 -} diff --git a/internal/wallet-connect/wallet_connect_test.go b/internal/wallet-connect/wallet_connect_test.go deleted file mode 100644 index 2aee923..0000000 --- a/internal/wallet-connect/wallet_connect_test.go +++ /dev/null @@ -1,106 +0,0 @@ -package walletconnect - -import ( - "crypto/ecdsa" - "encoding/hex" - "testing" - - "github.com/nspcc-dev/neo-go/pkg/crypto/keys" - "github.com/nspcc-dev/neofs-rest-gw/gen/models" - "github.com/nspcc-dev/neofs-rest-gw/internal/util" - "github.com/nspcc-dev/neofs-sdk-go/owner" - "github.com/stretchr/testify/require" -) - -const devenvPrivateKey = "1dd37fba80fec4e6a6f13fd708d8dcb3b29def768017052f6c930fa1c5d90bbb" - -func TestSign(t *testing.T) { - key, err := keys.NewPrivateKeyFromHex(devenvPrivateKey) - require.NoError(t, err) - - pubKeyHex := hex.EncodeToString(key.PublicKey().Bytes()) - - records := []*models.Record{{ - Operation: models.NewOperation(models.OperationPUT), - Action: models.NewAction(models.ActionALLOW), - Filters: []*models.Filter{}, - Targets: []*models.Target{{ - Role: models.NewRole(models.RoleOTHERS), - Keys: []string{}, - }}, - }} - - btoken, err := util.ToNativeObjectToken(records) - require.NoError(t, err) - - ownerKey, err := keys.NewPublicKeyFromString(pubKeyHex) - require.NoError(t, err) - - btoken.SetOwner(owner.NewIDFromPublicKey((*ecdsa.PublicKey)(ownerKey))) - - binaryBearer, err := btoken.ToV2().GetBody().StableMarshal(nil) - require.NoError(t, err) - - sm, err := SignMessage(&key.PrivateKey, binaryBearer) - require.NoError(t, err) - - verified := Verify((*ecdsa.PublicKey)(key.PublicKey()), binaryBearer, append(sm.Data, sm.Salt...)) - require.True(t, verified) -} - -func TestVerifyMessage(t *testing.T) { - testCases := [...]struct { - publicKey string - data string - salt string - messageHex string - messageOriginal string - }{ - { // Test values from this GIF https://github.com/CityOfZion/neon-wallet/pull/2390 . - publicKey: "02ce6228ba2cb2fc235be93aff9cd5fc0851702eb9791552f60db062f01e3d83f6", - data: "90ab1886ca0bece59b982d9ade8f5598065d651362fb9ce45ad66d0474b89c0b80913c8f0118a282acbdf200a429ba2d81bc52534a53ab41a2c6dfe2f0b4fb1b", - salt: "d41e348afccc2f3ee45cd9f5128b16dc", - messageHex: "010001f05c6434316533343861666363633266336565343563643966353132386231366463436172616c686f2c206d756c65712c206f2062616775697520656820697373756d65726d6f2074616978206c696761646f206e61206d697373e36f3f0000", - messageOriginal: "436172616c686f2c206d756c65712c206f2062616775697520656820697373756d65726d6f2074616978206c696761646f206e61206d697373e36f3f", - }, - { // Test value from wallet connect integration test - publicKey: "03bd9108c0b49f657e9eee50d1399022bd1e436118e5b7529a1b7cd606652f578f", - data: "510caa8cb6db5dedf04d215a064208d64be7496916d890df59aee132db8f2b07532e06f7ea664c4a99e3bcb74b43a35eb9653891b5f8701d2aef9e7526703eaa", - salt: "2c5b189569e92cce12e1c640f23e83ba", - messageHex: "010001f02632633562313839353639653932636365313265316336343066323365383362613132333435360000", - messageOriginal: "313233343536", // ascii string "123456" - }, - { // Test value from wallet connect integration test - publicKey: "03bd9108c0b49f657e9eee50d1399022bd1e436118e5b7529a1b7cd606652f578f", - data: "1e13f248962d8b3b60708b55ddf448d6d6a28c6b43887212a38b00bf6bab695e61261e54451c6e3d5f1f000e5534d166c7ca30f662a296d3a9aafa6d8c173c01", - salt: "58c86b2e74215b4f36b47d731236be3b", - messageHex: "010001f02035386338366232653734323135623466333662343764373331323336626533620000", - messageOriginal: "", // empty string - }, - } - - for _, testCase := range testCases { - pub, err := keys.NewPublicKeyFromString(testCase.publicKey) - require.NoError(t, err) - data, err := hex.DecodeString(testCase.data) - require.NoError(t, err) - salt, err := hex.DecodeString(testCase.salt) - require.NoError(t, err) - msg, err := hex.DecodeString(testCase.messageHex) - require.NoError(t, err) - orig, err := hex.DecodeString(testCase.messageOriginal) - require.NoError(t, err) - - require.Equal(t, msg, createMessageWithSalt(orig, salt)) - - sm := SignedMessage{ - Data: data, - Message: msg, - PublicKey: pub.Bytes(), - Salt: salt, - } - require.True(t, VerifyMessage(nil, sm)) - - require.True(t, Verify((*ecdsa.PublicKey)(pub), orig, append(data, salt...))) - } -}