From 0bf59522f72b3a5b4fe42334b43fdaebfe5f7780 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Mon, 28 Feb 2022 15:35:10 +0300 Subject: [PATCH] [#1191] object/acl: check session token verb Signed-off-by: Evgenii Stratonikov --- pkg/services/object/acl/v2/errors.go | 2 ++ pkg/services/object/acl/v2/service.go | 5 +++- pkg/services/object/acl/v2/util.go | 32 ++++++++++++++------ pkg/services/object/acl/v2/util_test.go | 39 +++++++++++++++++++++++++ 4 files changed, 68 insertions(+), 10 deletions(-) diff --git a/pkg/services/object/acl/v2/errors.go b/pkg/services/object/acl/v2/errors.go index 1219783eb..77c714c8b 100644 --- a/pkg/services/object/acl/v2/errors.go +++ b/pkg/services/object/acl/v2/errors.go @@ -13,6 +13,8 @@ var ( ErrUnknownRole = errors.New("can't classify request sender") // ErrUnknownContainer is returned when container fetching errors appeared. ErrUnknownContainer = errors.New("can't fetch container info") + // ErrInvalidVerb is returned when session token verb doesn't include necessary operation. + ErrInvalidVerb = errors.New("session token verb is invalid") ) type accessErr struct { diff --git a/pkg/services/object/acl/v2/service.go b/pkg/services/object/acl/v2/service.go index b24cad92e..43c131af8 100644 --- a/pkg/services/object/acl/v2/service.go +++ b/pkg/services/object/acl/v2/service.go @@ -425,7 +425,10 @@ func (b Service) findRequestInfo( } // find verb from token if it is present - verb := sourceVerbOfRequest(req, op) + verb, isUnknown := sourceVerbOfRequest(req.token, op) + if !isUnknown && verb != op && !isVerbCompatible(verb, op) { + return info, ErrInvalidVerb + } info.basicACL = cnr.BasicACL() info.requestRole = res.role diff --git a/pkg/services/object/acl/v2/util.go b/pkg/services/object/acl/v2/util.go index f55f58d86..1f683c8d1 100644 --- a/pkg/services/object/acl/v2/util.go +++ b/pkg/services/object/acl/v2/util.go @@ -102,18 +102,17 @@ func getObjectOwnerFromMessage(req interface{}) (id *owner.ID, err error) { } // sourceVerbOfRequest looks for verb in session token and if it is not found, -// returns reqVerb. -func sourceVerbOfRequest(req MetaWithToken, reqVerb eaclSDK.Operation) eaclSDK.Operation { - if req.token != nil { - switch v := req.token.Context().(type) { - case *sessionSDK.ObjectContext: - return tokenVerbToOperation(v) - default: - // do nothing, return request verb +// returns reqVerb. Second return value is true if operation is unknown. +func sourceVerbOfRequest(tok *sessionSDK.Token, reqVerb eaclSDK.Operation) (eaclSDK.Operation, bool) { + ctx, ok := tok.Context().(*sessionSDK.ObjectContext) + if ok { + op := tokenVerbToOperation(ctx) + if op != eaclSDK.OperationUnknown { + return op, false } } - return reqVerb + return reqVerb, true } func useObjectIDFromSession(req *RequestInfo, token *sessionSDK.Token) { @@ -195,3 +194,18 @@ func isOwnerFromKey(id *owner.ID, key *keys.PublicKey) bool { return id.Equal(owner.NewIDFromPublicKey((*ecdsa.PublicKey)(key))) } + +// isVerbCompatible checks that tokenVerb operation can create auxiliary op operation. +func isVerbCompatible(tokenVerb, op eaclSDK.Operation) bool { + switch tokenVerb { + case eaclSDK.OperationGet: + return op == eaclSDK.OperationGet || op == eaclSDK.OperationHead + case eaclSDK.OperationDelete: + return op == eaclSDK.OperationPut || op == eaclSDK.OperationHead || + op == eaclSDK.OperationSearch + case eaclSDK.OperationRange, eaclSDK.OperationRangeHash: + return op == eaclSDK.OperationRange || op == eaclSDK.OperationHead + default: + return tokenVerb == op + } +} diff --git a/pkg/services/object/acl/v2/util_test.go b/pkg/services/object/acl/v2/util_test.go index d17a9c415..5edb3b9ba 100644 --- a/pkg/services/object/acl/v2/util_test.go +++ b/pkg/services/object/acl/v2/util_test.go @@ -7,6 +7,7 @@ import ( acltest "github.com/nspcc-dev/neofs-api-go/v2/acl/test" "github.com/nspcc-dev/neofs-api-go/v2/session" sessiontest "github.com/nspcc-dev/neofs-api-go/v2/session/test" + "github.com/nspcc-dev/neofs-sdk-go/eacl" sessionSDK "github.com/nspcc-dev/neofs-sdk-go/session" bearerSDK "github.com/nspcc-dev/neofs-sdk-go/token" "github.com/stretchr/testify/require" @@ -36,3 +37,41 @@ func testGenerateMetaHeader(depth uint32, b *acl.BearerToken, s *session.Session return metaHeader } + +func TestIsVerbCompatible(t *testing.T) { + // Source: https://nspcc.ru/upload/neofs-spec-latest.pdf#page=28 + table := map[eacl.Operation][]eacl.Operation{ + eacl.OperationPut: {eacl.OperationPut}, + eacl.OperationDelete: {eacl.OperationPut, eacl.OperationHead, eacl.OperationSearch}, + eacl.OperationHead: {eacl.OperationHead}, + eacl.OperationRange: {eacl.OperationRange, eacl.OperationHead}, + eacl.OperationRangeHash: {eacl.OperationRange, eacl.OperationHead}, + eacl.OperationGet: {eacl.OperationGet, eacl.OperationHead}, + eacl.OperationSearch: {eacl.OperationSearch}, + } + + ops := []eacl.Operation{ + eacl.OperationPut, + eacl.OperationDelete, + eacl.OperationHead, + eacl.OperationRange, + eacl.OperationRangeHash, + eacl.OperationGet, + eacl.OperationSearch, + } + + for _, opToken := range ops { + for _, op := range ops { + var contains bool + for _, o := range table[opToken] { + if o == op { + contains = true + break + } + } + + require.Equal(t, contains, isVerbCompatible(opToken, op), + "%s in token, %s executing", opToken, op) + } + } +}