frostfs-node/pkg/services/object/acl/v2/util.go

212 lines
6.3 KiB
Go

package v2
import (
"crypto/ecdsa"
"crypto/elliptic"
"errors"
"fmt"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
objectV2 "github.com/nspcc-dev/neofs-api-go/v2/object"
refsV2 "github.com/nspcc-dev/neofs-api-go/v2/refs"
sessionV2 "github.com/nspcc-dev/neofs-api-go/v2/session"
containerIDSDK "github.com/nspcc-dev/neofs-sdk-go/container/id"
eaclSDK "github.com/nspcc-dev/neofs-sdk-go/eacl"
oidSDK "github.com/nspcc-dev/neofs-sdk-go/object/id"
"github.com/nspcc-dev/neofs-sdk-go/owner"
sessionSDK "github.com/nspcc-dev/neofs-sdk-go/session"
"github.com/nspcc-dev/neofs-sdk-go/signature"
bearerSDK "github.com/nspcc-dev/neofs-sdk-go/token"
)
func getContainerIDFromRequest(req interface{}) (id *containerIDSDK.ID, err error) {
switch v := req.(type) {
case *objectV2.GetRequest:
return containerIDSDK.NewFromV2(v.GetBody().GetAddress().GetContainerID()), nil
case *objectV2.PutRequest:
objPart := v.GetBody().GetObjectPart()
if part, ok := objPart.(*objectV2.PutObjectPartInit); ok {
return containerIDSDK.NewFromV2(part.GetHeader().GetContainerID()), nil
}
return nil, errors.New("can't get container ID in chunk")
case *objectV2.HeadRequest:
return containerIDSDK.NewFromV2(v.GetBody().GetAddress().GetContainerID()), nil
case *objectV2.SearchRequest:
return containerIDSDK.NewFromV2(v.GetBody().GetContainerID()), nil
case *objectV2.DeleteRequest:
return containerIDSDK.NewFromV2(v.GetBody().GetAddress().GetContainerID()), nil
case *objectV2.GetRangeRequest:
return containerIDSDK.NewFromV2(v.GetBody().GetAddress().GetContainerID()), nil
case *objectV2.GetRangeHashRequest:
return containerIDSDK.NewFromV2(v.GetBody().GetAddress().GetContainerID()), nil
default:
return nil, errors.New("unknown request type")
}
}
// originalBearerToken goes down to original request meta header and fetches
// bearer token from there.
func originalBearerToken(header *sessionV2.RequestMetaHeader) *bearerSDK.BearerToken {
for header.GetOrigin() != nil {
header = header.GetOrigin()
}
return bearerSDK.NewBearerTokenFromV2(header.GetBearerToken())
}
// originalSessionToken goes down to original request meta header and fetches
// session token from there.
func originalSessionToken(header *sessionV2.RequestMetaHeader) *sessionSDK.Token {
for header.GetOrigin() != nil {
header = header.GetOrigin()
}
return sessionSDK.NewTokenFromV2(header.GetSessionToken())
}
func getObjectIDFromRequestBody(body interface{}) *oidSDK.ID {
switch v := body.(type) {
default:
return nil
case interface {
GetObjectID() *refsV2.ObjectID
}:
return oidSDK.NewIDFromV2(v.GetObjectID())
case interface {
GetAddress() *refsV2.Address
}:
return oidSDK.NewIDFromV2(v.GetAddress().GetObjectID())
}
}
func getObjectOwnerFromMessage(req interface{}) (id *owner.ID, err error) {
switch v := req.(type) {
case *objectV2.PutRequest:
objPart := v.GetBody().GetObjectPart()
if part, ok := objPart.(*objectV2.PutObjectPartInit); ok {
return owner.NewIDFromV2(part.GetHeader().GetOwnerID()), nil
}
return nil, errors.New("can't get container ID in chunk")
case *objectV2.GetResponse:
objPart := v.GetBody().GetObjectPart()
if part, ok := objPart.(*objectV2.GetObjectPartInit); ok {
return owner.NewIDFromV2(part.GetHeader().GetOwnerID()), nil
}
return nil, errors.New("can't get container ID in chunk")
default:
return nil, errors.New("unsupported request type")
}
}
// sourceVerbOfRequest looks for verb in session token and if it is not found,
// 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, true
}
func useObjectIDFromSession(req *RequestInfo, token *sessionSDK.Token) {
if token == nil {
return
}
objCtx, ok := token.Context().(*sessionSDK.ObjectContext)
if !ok {
return
}
req.oid = objCtx.Address().ObjectID()
}
func tokenVerbToOperation(ctx *sessionSDK.ObjectContext) eaclSDK.Operation {
switch {
case ctx.IsForGet():
return eaclSDK.OperationGet
case ctx.IsForPut():
return eaclSDK.OperationPut
case ctx.IsForHead():
return eaclSDK.OperationHead
case ctx.IsForSearch():
return eaclSDK.OperationSearch
case ctx.IsForDelete():
return eaclSDK.OperationDelete
case ctx.IsForRange():
return eaclSDK.OperationRange
case ctx.IsForRangeHash():
return eaclSDK.OperationRangeHash
default:
return eaclSDK.OperationUnknown
}
}
func ownerFromToken(token *sessionSDK.Token) (*owner.ID, *keys.PublicKey, error) {
// 1. First check signature of session token.
if !token.VerifySignature() {
return nil, nil, fmt.Errorf("%w: invalid session token signature", ErrMalformedRequest)
}
// 2. Then check if session token owner issued the session token
tokenIssuerKey := unmarshalPublicKey(token.Signature().Key())
tokenOwner := token.OwnerID()
if !isOwnerFromKey(tokenOwner, tokenIssuerKey) {
// TODO: #767 in this case we can issue all owner keys from neofs.id and check once again
return nil, nil, fmt.Errorf("%w: invalid session token owner", ErrMalformedRequest)
}
return tokenOwner, tokenIssuerKey, nil
}
func originalBodySignature(v *sessionV2.RequestVerificationHeader) *signature.Signature {
if v == nil {
return nil
}
for v.GetOrigin() != nil {
v = v.GetOrigin()
}
return signature.NewFromV2(v.GetBodySignature())
}
func unmarshalPublicKey(bs []byte) *keys.PublicKey {
pub, err := keys.NewPublicKeyFromBytes(bs, elliptic.P256())
if err != nil {
return nil
}
return pub
}
func isOwnerFromKey(id *owner.ID, key *keys.PublicKey) bool {
if id == nil || key == nil {
return false
}
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
}
}