diff --git a/cmd/neofs-rest-gw/integration_test.go b/cmd/neofs-rest-gw/integration_test.go index 213fb13..f4d94a8 100644 --- a/cmd/neofs-rest-gw/integration_test.go +++ b/cmd/neofs-rest-gw/integration_test.go @@ -123,6 +123,7 @@ func runTests(ctx context.Context, t *testing.T, key *keys.PrivateKey, version s 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, &owner, cnrID) }) + t.Run("rest get object unauthenticated "+version, func(t *testing.T) { restObjectGetUnauthenticated(ctx, t, clientPool, &owner, cnrID) }) t.Run("rest get object full bearer "+version, func(t *testing.T) { restObjectGetFullBearer(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) }) @@ -630,6 +631,53 @@ func restObjectGet(ctx context.Context, t *testing.T, p *pool.Pool, ownerID *use require.Equal(t, int64(0), *objInfo2.ObjectSize) } +func restObjectGetUnauthenticated(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-unauth-name", + "user-attribute": "user value", + } + + objID := createObject(ctx, t, p, ownerID, cnrID, attributes, content) + + httpClient := defaultHTTPClient() + + request, err := http.NewRequest(http.MethodGet, testHost+"/v1/objects/"+cnrID.EncodeToString()+"/"+objID.EncodeToString(), nil) + require.NoError(t, err) + + request.Header.Add("Content-Type", "application/json") + + resp := &models.ErrorResponse{} + doRequest(t, httpClient, request, http.StatusBadRequest, resp) + require.Equal(t, int64(2048), resp.Code) + require.Equal(t, models.ErrorTypeAPI, *resp.Type) + + // set empty eacl table to be able to do unauthenticated request + allowByEACL(ctx, t, p, cnrID) + + request, err = http.NewRequest(http.MethodGet, testHost+"/v1/objects/"+cnrID.EncodeToString()+"/"+objID.EncodeToString(), nil) + require.NoError(t, err) + objInfo := &models.ObjectInfo{} + doRequest(t, httpClient, request, http.StatusOK, objInfo) + + 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) + + contentData, err := base64.StdEncoding.DecodeString(objInfo.Payload) + require.NoError(t, err) + require.Equal(t, content, contentData) + + for _, attr := range objInfo.Attributes { + require.Equal(t, attributes[*attr.Key], *attr.Value) + } + + // set eacl the same as was before test started + restrictByEACL(ctx, t, p, cnrID) +} + func restObjectGetFullBearer(ctx context.Context, t *testing.T, p *pool.Pool, ownerID *user.ID, cnrID cid.ID) { content := []byte("some content") attributes := map[string]string{ @@ -1298,3 +1346,21 @@ func restrictByEACL(ctx context.Context, t *testing.T, clientPool *pool.Pool, cn return table } + +func allowByEACL(ctx context.Context, t *testing.T, clientPool *pool.Pool, cnrID cid.ID) *eacl.Table { + table := eacl.NewTable() + table.SetCID(cnrID) + + var waitPrm pool.WaitParams + waitPrm.SetPollInterval(3 * time.Second) + waitPrm.SetTimeout(15 * time.Second) + + var prm pool.PrmContainerSetEACL + prm.SetTable(*table) + prm.SetWaitParams(waitPrm) + + err := clientPool.SetEACL(ctx, prm) + require.NoError(t, err) + + return table +} diff --git a/gen/restapi/embedded_spec.go b/gen/restapi/embedded_spec.go index 88a6337..d99e6f3 100644 --- a/gen/restapi/embedded_spec.go +++ b/gen/restapi/embedded_spec.go @@ -599,6 +599,12 @@ func init() { }, "/objects/{containerId}/search": { "post": { + "security": [ + {}, + { + "BearerAuth": [] + } + ], "summary": "Search objects by filters", "operationId": "searchObjects", "parameters": [ @@ -685,6 +691,12 @@ func init() { }, "/objects/{containerId}/{objectId}": { "get": { + "security": [ + {}, + { + "BearerAuth": [] + } + ], "summary": "Get object info by address", "operationId": "getObjectInfo", "parameters": [ @@ -2298,6 +2310,12 @@ func init() { }, "/objects/{containerId}/search": { "post": { + "security": [ + {}, + { + "BearerAuth": [] + } + ], "summary": "Search objects by filters", "operationId": "searchObjects", "parameters": [ @@ -2403,6 +2421,12 @@ func init() { }, "/objects/{containerId}/{objectId}": { "get": { + "security": [ + {}, + { + "BearerAuth": [] + } + ], "summary": "Get object info by address", "operationId": "getObjectInfo", "parameters": [ diff --git a/handlers/auth.go b/handlers/auth.go index a33f047..760903e 100644 --- a/handlers/auth.go +++ b/handlers/auth.go @@ -106,13 +106,7 @@ func (a *API) PostAuth(params operations.AuthParams) middleware.Responder { // FormBinaryBearer handler that forms binary bearer token using headers with body and signature. func (a *API) FormBinaryBearer(params operations.FormBinaryBearerParams, principal *models.Principal) middleware.Responder { - bearerHeaders, err := prepareBearerTokenHeaders(params.XBearerSignature, params.XBearerSignatureKey, *params.WalletConnect, false) - if err != nil { - resp := a.logAndGetErrorResponse("invalid bearer headers", err) - return operations.NewFormBinaryBearerBadRequest().WithPayload(resp) - } - - btoken, err := getBearerToken(principal, bearerHeaders) + btoken, err := getBearerToken(principal, params.XBearerSignature, params.XBearerSignatureKey, *params.WalletConnect, false) if err != nil { resp := a.logAndGetErrorResponse("invalid bearer token", err) return operations.NewFormBinaryBearerBadRequest().WithPayload(resp) diff --git a/handlers/objects.go b/handlers/objects.go index 69bac0a..01f7af6 100644 --- a/handlers/objects.go +++ b/handlers/objects.go @@ -33,13 +33,7 @@ func (a *API) PutObjects(params operations.PutObjectParams, principal *models.Pr errorResponse := operations.NewPutObjectBadRequest() ctx := params.HTTPRequest.Context() - bearerHeaders, err := prepareBearerTokenHeaders(params.XBearerSignature, params.XBearerSignatureKey, *params.WalletConnect, *params.FullBearer) - if err != nil { - resp := a.logAndGetErrorResponse("invalid bearer headers", err) - return errorResponse.WithPayload(resp) - } - - btoken, err := getBearerToken(principal, bearerHeaders) + btoken, err := getBearerToken(principal, params.XBearerSignature, params.XBearerSignatureKey, *params.WalletConnect, *params.FullBearer) if err != nil { resp := a.logAndGetErrorResponse("invalid bearer token", err) return errorResponse.WithPayload(resp) @@ -67,17 +61,15 @@ 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(&owner) + attachOwner(obj, btoken) obj.SetPayload(payload) obj.SetAttributes(attributes...) var prmPut pool.PrmObjectPut prmPut.SetHeader(*obj) - prmPut.UseBearer(btoken) + attachBearer(&prmPut, btoken) objID, err := a.pool.PutObject(ctx, prmPut) if err != nil { @@ -105,13 +97,7 @@ func (a *API) GetObjectInfo(params operations.GetObjectInfoParams, principal *mo return errorResponse.WithPayload(resp) } - bearerHeaders, err := prepareBearerTokenHeaders(params.XBearerSignature, params.XBearerSignatureKey, *params.WalletConnect, *params.FullBearer) - if err != nil { - resp := a.logAndGetErrorResponse("invalid bearer headers", err) - return errorResponse.WithPayload(resp) - } - - btoken, err := getBearerToken(principal, bearerHeaders) + btoken, err := getBearerToken(principal, params.XBearerSignature, params.XBearerSignatureKey, *params.WalletConnect, *params.FullBearer) if err != nil { resp := a.logAndGetErrorResponse("get bearer token", err) return errorResponse.WithPayload(resp) @@ -119,7 +105,7 @@ func (a *API) GetObjectInfo(params operations.GetObjectInfoParams, principal *mo var prm pool.PrmObjectHead prm.SetAddress(addr) - prm.UseBearer(btoken) + attachBearer(&prm, btoken) objInfo, err := a.pool.HeadObject(ctx, prm) if err != nil { @@ -158,7 +144,7 @@ func (a *API) GetObjectInfo(params operations.GetObjectInfoParams, principal *mo var prmRange pool.PrmObjectRange prmRange.SetAddress(addr) - prmRange.UseBearer(btoken) + attachBearer(&prmRange, btoken) prmRange.SetOffset(offset) prmRange.SetLength(length) @@ -205,13 +191,7 @@ func (a *API) DeleteObject(params operations.DeleteObjectParams, principal *mode return errorResponse.WithPayload(resp) } - bearerHeaders, err := prepareBearerTokenHeaders(params.XBearerSignature, params.XBearerSignatureKey, *params.WalletConnect, *params.FullBearer) - if err != nil { - resp := a.logAndGetErrorResponse("invalid bearer headers", err) - return errorResponse.WithPayload(resp) - } - - btoken, err := getBearerToken(principal, bearerHeaders) + btoken, err := getBearerToken(principal, params.XBearerSignature, params.XBearerSignatureKey, *params.WalletConnect, *params.FullBearer) if err != nil { resp := a.logAndGetErrorResponse("failed to get bearer token", err) return errorResponse.WithPayload(resp) @@ -219,7 +199,7 @@ func (a *API) DeleteObject(params operations.DeleteObjectParams, principal *mode var prm pool.PrmObjectDelete prm.SetAddress(addr) - prm.UseBearer(btoken) + attachBearer(&prm, btoken) if err = a.pool.DeleteObject(ctx, prm); err != nil { resp := a.logAndGetErrorResponse("failed to delete object", err) @@ -242,13 +222,7 @@ func (a *API) SearchObjects(params operations.SearchObjectsParams, principal *mo return errorResponse.WithPayload(resp) } - bearerHeaders, err := prepareBearerTokenHeaders(params.XBearerSignature, params.XBearerSignatureKey, *params.WalletConnect, *params.FullBearer) - if err != nil { - resp := a.logAndGetErrorResponse("invalid bearer headers", err) - return errorResponse.WithPayload(resp) - } - - btoken, err := getBearerToken(principal, bearerHeaders) + btoken, err := getBearerToken(principal, params.XBearerSignature, params.XBearerSignatureKey, *params.WalletConnect, *params.FullBearer) if err != nil { resp := a.logAndGetErrorResponse("failed to get bearer token", err) return errorResponse.WithPayload(resp) @@ -262,7 +236,7 @@ func (a *API) SearchObjects(params operations.SearchObjectsParams, principal *mo var prm pool.PrmObjectSearch prm.SetContainerID(cnrID) - prm.UseBearer(btoken) + attachBearer(&prm, btoken) prm.SetFilters(filters) resSearch, err := a.pool.SearchObjects(ctx, prm) @@ -311,14 +285,14 @@ func (a *API) SearchObjects(params operations.SearchObjectsParams, principal *mo WithAccessControlAllowOrigin("*") } -func headObjectBaseInfo(ctx context.Context, p *pool.Pool, cnrID cid.ID, objID oid.ID, btoken bearer.Token) (*models.ObjectBaseInfo, error) { +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.UseBearer(btoken) + attachBearer(&prm, btoken) objInfo, err := p.HeadObject(ctx, prm) if err != nil { @@ -361,54 +335,56 @@ func parseAddress(containerID, objectID string) (oid.Address, error) { return addr, nil } -type BearerTokenHeaders struct { - Signature string - Key string - IsWalletConnect bool - IsFullToken bool -} - -func getBearerToken(token *models.Principal, hdr *BearerTokenHeaders) (bearer.Token, error) { - bt := &BearerToken{ - Token: string(*token), - Signature: hdr.Signature, - Key: hdr.Key, +func getBearerToken(token *models.Principal, signature, key *string, isWalletConnect, isFullToken bool) (*bearer.Token, error) { + if token == nil { + return nil, nil } - return prepareBearerToken(bt, hdr.IsWalletConnect, hdr.IsFullToken) + bt := &BearerToken{Token: string(*token)} + + if !isFullToken { + if signature == nil || key == nil { + return nil, errors.New("missed signature or key header") + } + + bt.Signature = *signature + bt.Key = *key + } + + return prepareBearerToken(bt, isWalletConnect, isFullToken) } -func prepareBearerToken(bt *BearerToken, isWalletConnect, isFullToken bool) (bearer.Token, error) { +func prepareBearerToken(bt *BearerToken, isWalletConnect, isFullToken bool) (*bearer.Token, error) { data, err := base64.StdEncoding.DecodeString(bt.Token) if err != nil { - return bearer.Token{}, fmt.Errorf("can't base64-decode bearer token: %w", err) + return nil, fmt.Errorf("can't base64-decode bearer token: %w", err) } if isFullToken { var btoken bearer.Token if err = btoken.Unmarshal(data); err != nil { - return bearer.Token{}, fmt.Errorf("couldn't unmarshall bearer token: %w", err) + return nil, fmt.Errorf("couldn't unmarshall bearer token: %w", err) } if !btoken.VerifySignature() { - return bearer.Token{}, fmt.Errorf("invalid signature") + return nil, fmt.Errorf("invalid signature") } - return btoken, nil + return &btoken, nil } signature, err := hex.DecodeString(bt.Signature) if err != nil { - return bearer.Token{}, fmt.Errorf("couldn't decode bearer signature: %w", err) + return nil, fmt.Errorf("couldn't decode bearer signature: %w", err) } ownerKey, err := keys.NewPublicKeyFromString(bt.Key) if err != nil { - return bearer.Token{}, fmt.Errorf("couldn't fetch bearer token owner key: %w", err) + return nil, fmt.Errorf("couldn't fetch bearer token owner key: %w", err) } body := new(acl.BearerTokenBody) if err = body.Unmarshal(data); err != nil { - return bearer.Token{}, fmt.Errorf("can't unmarshal bearer token body: %w", err) + return nil, fmt.Errorf("can't unmarshal bearer token body: %w", err) } v2signature := new(refs.Signature) @@ -425,14 +401,14 @@ func prepareBearerToken(bt *BearerToken, isWalletConnect, isFullToken bool) (bea var btoken bearer.Token if err = btoken.ReadFromV2(v2btoken); err != nil { - return bearer.Token{}, fmt.Errorf("read from v2 token: %w", err) + return nil, fmt.Errorf("read from v2 token: %w", err) } if !btoken.VerifySignature() { - return bearer.Token{}, fmt.Errorf("invalid signature") + return nil, fmt.Errorf("invalid signature") } - return btoken, nil + return &btoken, nil } func prepareOffsetLength(params operations.GetObjectInfoParams, objSize uint64) (uint64, uint64, error) { @@ -457,3 +433,19 @@ func prepareOffsetLength(params operations.GetObjectInfoParams, objSize uint64) return offset, length, nil } + +type prmWithBearer interface { + UseBearer(token bearer.Token) +} + +func attachBearer(prm prmWithBearer, btoken *bearer.Token) { + if btoken != nil { + prm.UseBearer(*btoken) + } +} +func attachOwner(obj *object.Object, btoken *bearer.Token) { + if btoken != nil { + owner := bearer.ResolveIssuer(*btoken) + obj.SetOwnerID(&owner) + } +} diff --git a/handlers/util.go b/handlers/util.go index 100385e..c732fa8 100644 --- a/handlers/util.go +++ b/handlers/util.go @@ -188,25 +188,6 @@ func IsObjectToken(token *models.Bearer) (bool, error) { return isObject, nil } -func prepareBearerTokenHeaders(signature, key *string, isWalletConnect, isFullToken bool) (*BearerTokenHeaders, error) { - bearerHeaders := &BearerTokenHeaders{ - IsWalletConnect: isWalletConnect, - IsFullToken: isFullToken, - } - if isFullToken { - return bearerHeaders, nil - } - - if signature == nil || key == nil { - return nil, errors.New("missed signature or key header") - } - - bearerHeaders.Signature = *signature - bearerHeaders.Key = *key - - return bearerHeaders, nil -} - func formSessionTokenFromHeaders(principal *models.Principal, signature, key *string, verb sessionv2.ContainerSessionVerb) (*SessionToken, error) { if signature == nil || key == nil { return nil, errors.New("missed signature or key header") diff --git a/spec/rest.yaml b/spec/rest.yaml index 704dddb..4f3ba57 100644 --- a/spec/rest.yaml +++ b/spec/rest.yaml @@ -240,6 +240,9 @@ paths: post: operationId: searchObjects summary: Search objects by filters + security: + - {} + - BearerAuth: [ ] parameters: - $ref: '#/parameters/signatureParam' - $ref: '#/parameters/signatureKeyParam' @@ -297,6 +300,9 @@ paths: get: operationId: getObjectInfo summary: Get object info by address + security: + - {} + - BearerAuth: [ ] parameters: - $ref: '#/parameters/signatureParam' - $ref: '#/parameters/signatureKeyParam'