forked from TrueCloudLab/frostfs-sdk-go
[#197] session: Refactor and document the package
Signed-off-by: Leonard Lyubich <leonard@nspcc.ru>
This commit is contained in:
parent
497053c785
commit
552c7875bf
32 changed files with 1622 additions and 1358 deletions
|
@ -1,166 +1,379 @@
|
|||
package session
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ecdsa"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/refs"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/session"
|
||||
neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto"
|
||||
neofsecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/object/address"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/user"
|
||||
)
|
||||
|
||||
// ObjectContext represents NeoFS API v2-compatible
|
||||
// context of the object session.
|
||||
// Object represents token of the NeoFS Object session. A session is opened
|
||||
// between any two sides of the system, and implements a mechanism for transferring
|
||||
// the power of attorney of actions to another network member. The session has a
|
||||
// limited validity period, and applies to a strictly defined set of operations.
|
||||
// See methods for details.
|
||||
//
|
||||
// It is a wrapper over session.ObjectSessionContext
|
||||
// which allows abstracting from details of the message
|
||||
// structure.
|
||||
type ObjectContext session.ObjectSessionContext
|
||||
|
||||
// NewObjectContext creates and returns blank ObjectContext.
|
||||
// Object is mutually compatible with github.com/nspcc-dev/neofs-api-go/v2/session.Token
|
||||
// message. See ReadFromV2 / WriteToV2 methods.
|
||||
//
|
||||
// Defaults:
|
||||
// - not bound to any operation;
|
||||
// - nil object address.
|
||||
func NewObjectContext() *ObjectContext {
|
||||
v2 := new(session.ObjectSessionContext)
|
||||
// Instances can be created using built-in var declaration.
|
||||
type Object struct {
|
||||
lt session.TokenLifetime
|
||||
|
||||
return NewObjectContextFromV2(v2)
|
||||
obj refs.Address
|
||||
|
||||
c session.ObjectSessionContext
|
||||
|
||||
body session.TokenBody
|
||||
|
||||
sig neofscrypto.Signature
|
||||
}
|
||||
|
||||
// NewObjectContextFromV2 wraps session.ObjectSessionContext
|
||||
// into ObjectContext.
|
||||
func NewObjectContextFromV2(v *session.ObjectSessionContext) *ObjectContext {
|
||||
return (*ObjectContext)(v)
|
||||
// ReadFromV2 reads Object from the session.Token message.
|
||||
//
|
||||
// See also WriteToV2.
|
||||
func (x *Object) ReadFromV2(m session.Token) error {
|
||||
b := m.GetBody()
|
||||
if b == nil {
|
||||
return errors.New("missing body")
|
||||
}
|
||||
|
||||
bID := b.GetID()
|
||||
var id uuid.UUID
|
||||
|
||||
err := id.UnmarshalBinary(bID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid binary ID: %w", err)
|
||||
} else if ver := id.Version(); ver != 4 {
|
||||
return fmt.Errorf("invalid UUID version %s", ver)
|
||||
}
|
||||
|
||||
c, ok := b.GetContext().(*session.ObjectSessionContext)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid context %T", b.GetContext())
|
||||
}
|
||||
|
||||
x.body = *b
|
||||
|
||||
if c != nil {
|
||||
x.c = *c
|
||||
|
||||
obj := c.GetAddress()
|
||||
if obj != nil {
|
||||
x.obj = *obj
|
||||
} else {
|
||||
x.obj = refs.Address{}
|
||||
}
|
||||
} else {
|
||||
x.c = session.ObjectSessionContext{}
|
||||
x.obj = refs.Address{}
|
||||
}
|
||||
|
||||
lt := b.GetLifetime()
|
||||
if lt != nil {
|
||||
x.lt = *lt
|
||||
} else {
|
||||
x.lt = session.TokenLifetime{}
|
||||
}
|
||||
|
||||
sig := m.GetSignature()
|
||||
if sig != nil {
|
||||
x.sig.ReadFromV2(*sig)
|
||||
} else {
|
||||
x.sig = neofscrypto.Signature{}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToV2 converts ObjectContext to session.ObjectSessionContext
|
||||
// message structure.
|
||||
func (x *ObjectContext) ToV2() *session.ObjectSessionContext {
|
||||
return (*session.ObjectSessionContext)(x)
|
||||
// WriteToV2 writes Object to the session.Token message.
|
||||
// The message must not be nil.
|
||||
//
|
||||
// See also ReadFromV2.
|
||||
func (x Object) WriteToV2(m *session.Token) {
|
||||
var sig refs.Signature
|
||||
x.sig.WriteToV2(&sig)
|
||||
|
||||
m.SetBody(&x.body)
|
||||
m.SetSignature(&sig)
|
||||
}
|
||||
|
||||
// ApplyTo specifies which object the ObjectContext applies to.
|
||||
func (x *ObjectContext) ApplyTo(id *address.Address) {
|
||||
v2 := (*session.ObjectSessionContext)(x)
|
||||
// Marshal encodes Object into a binary format of the NeoFS API protocol
|
||||
// (Protocol Buffers with direct field order).
|
||||
//
|
||||
// See also Unmarshal.
|
||||
func (x Object) Marshal() []byte {
|
||||
var m session.Token
|
||||
x.WriteToV2(&m)
|
||||
|
||||
v2.SetAddress(id.ToV2())
|
||||
data, err := m.StableMarshal(nil)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("unexpected error from Token.StableMarshal: %v", err))
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
// Address returns identifier of the object
|
||||
// to which the ObjectContext applies.
|
||||
func (x *ObjectContext) Address() *address.Address {
|
||||
v2 := (*session.ObjectSessionContext)(x)
|
||||
// Unmarshal decodes NeoFS API protocol binary format into the Object
|
||||
// (Protocol Buffers with direct field order). Returns an error describing
|
||||
// a format violation.
|
||||
//
|
||||
// See also Marshal.
|
||||
func (x *Object) Unmarshal(data []byte) error {
|
||||
var m session.Token
|
||||
|
||||
return address.NewAddressFromV2(v2.GetAddress())
|
||||
err := m.Unmarshal(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return x.ReadFromV2(m)
|
||||
}
|
||||
|
||||
func (x *ObjectContext) forVerb(v session.ObjectSessionVerb) {
|
||||
(*session.ObjectSessionContext)(x).
|
||||
SetVerb(v)
|
||||
// MarshalJSON encodes Object into a JSON format of the NeoFS API protocol
|
||||
// (Protocol Buffers JSON).
|
||||
//
|
||||
// See also UnmarshalJSON.
|
||||
func (x Object) MarshalJSON() ([]byte, error) {
|
||||
var m session.Token
|
||||
x.WriteToV2(&m)
|
||||
|
||||
return m.MarshalJSON()
|
||||
}
|
||||
|
||||
func (x *ObjectContext) isForVerb(v session.ObjectSessionVerb) bool {
|
||||
return (*session.ObjectSessionContext)(x).
|
||||
GetVerb() == v
|
||||
// UnmarshalJSON decodes NeoFS API protocol JSON format into the Object
|
||||
// (Protocol Buffers JSON). Returns an error describing a format violation.
|
||||
//
|
||||
// See also MarshalJSON.
|
||||
func (x *Object) UnmarshalJSON(data []byte) error {
|
||||
var m session.Token
|
||||
|
||||
err := m.UnmarshalJSON(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return x.ReadFromV2(m)
|
||||
}
|
||||
|
||||
// ForPut binds the ObjectContext to
|
||||
// PUT operation.
|
||||
func (x *ObjectContext) ForPut() {
|
||||
x.forVerb(session.ObjectVerbPut)
|
||||
// Sign calculates and writes signature of the Object data.
|
||||
// Returns signature calculation errors.
|
||||
//
|
||||
// Zero Object is unsigned.
|
||||
//
|
||||
// Note that any Object mutation is likely to break the signature, so it is
|
||||
// expected to be calculated as a final stage of Object formation.
|
||||
//
|
||||
// See also VerifySignature.
|
||||
func (x *Object) Sign(key ecdsa.PrivateKey) error {
|
||||
var idUser user.ID
|
||||
user.IDFromKey(&idUser, key.PublicKey)
|
||||
|
||||
var idUserV2 refs.OwnerID
|
||||
idUser.WriteToV2(&idUserV2)
|
||||
|
||||
x.c.SetAddress(&x.obj)
|
||||
|
||||
x.body.SetOwnerID(&idUserV2)
|
||||
x.body.SetLifetime(&x.lt)
|
||||
x.body.SetContext(&x.c)
|
||||
|
||||
data, err := x.body.StableMarshal(nil)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("unexpected error from Token.StableMarshal: %v", err))
|
||||
}
|
||||
|
||||
return x.sig.Calculate(neofsecdsa.Signer(key), data)
|
||||
}
|
||||
|
||||
// IsForPut checks if ObjectContext is bound to
|
||||
// PUT operation.
|
||||
func (x *ObjectContext) IsForPut() bool {
|
||||
return x.isForVerb(session.ObjectVerbPut)
|
||||
// VerifySignature checks if Object signature is presented and valid.
|
||||
//
|
||||
// Zero Object fails the check.
|
||||
//
|
||||
// See also Sign.
|
||||
func (x Object) VerifySignature() bool {
|
||||
// TODO: check owner<->key relation
|
||||
data, err := x.body.StableMarshal(nil)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("unexpected error from Token.StableMarshal: %v", err))
|
||||
}
|
||||
|
||||
return x.sig.Verify(data)
|
||||
}
|
||||
|
||||
// ForDelete binds the ObjectContext to
|
||||
// DELETE operation.
|
||||
func (x *ObjectContext) ForDelete() {
|
||||
x.forVerb(session.ObjectVerbDelete)
|
||||
// ApplyTo limits session scope to a given author object.
|
||||
//
|
||||
// See also AppliedTo.
|
||||
func (x *Object) ApplyTo(a address.Address) {
|
||||
x.obj = *a.ToV2()
|
||||
}
|
||||
|
||||
// IsForDelete checks if ObjectContext is bound to
|
||||
// DELETE operation.
|
||||
func (x *ObjectContext) IsForDelete() bool {
|
||||
return x.isForVerb(session.ObjectVerbDelete)
|
||||
// AppliedTo checks if session scope is limited by a given object.
|
||||
//
|
||||
// Zero Object isn't applied to any author's object.
|
||||
//
|
||||
// See also ApplyTo.
|
||||
func (x Object) AppliedTo(obj address.Address) bool {
|
||||
objv2 := *address.NewAddressFromV2(&x.obj)
|
||||
|
||||
// FIXME: use Equals method
|
||||
return obj.String() == objv2.String()
|
||||
}
|
||||
|
||||
// ForGet binds the ObjectContext to
|
||||
// GET operation.
|
||||
func (x *ObjectContext) ForGet() {
|
||||
x.forVerb(session.ObjectVerbGet)
|
||||
// ObjectVerb enumerates object operations.
|
||||
type ObjectVerb int8
|
||||
|
||||
const (
|
||||
_ ObjectVerb = iota
|
||||
|
||||
VerbObjectPut // Put rpc
|
||||
VerbObjectGet // Get rpc
|
||||
VerbObjectHead // Head rpc
|
||||
VerbObjectSearch // Search rpc
|
||||
VerbObjectDelete // Delete rpc
|
||||
VerbObjectRange // GetRange rpc
|
||||
VerbObjectRangeHash // GetRangeHash rpc
|
||||
)
|
||||
|
||||
// ForVerb specifies the object operation of the session scope. Each
|
||||
// Object is related to the single operation.
|
||||
//
|
||||
// See also AssertVerb.
|
||||
func (x *Object) ForVerb(verb ObjectVerb) {
|
||||
x.c.SetVerb(session.ObjectSessionVerb(verb))
|
||||
}
|
||||
|
||||
// IsForGet checks if ObjectContext is bound to
|
||||
// GET operation.
|
||||
func (x *ObjectContext) IsForGet() bool {
|
||||
return x.isForVerb(session.ObjectVerbGet)
|
||||
// AssertVerb checks if Object relates to one of the given object operations.
|
||||
//
|
||||
// Zero Object relates to zero (unspecified) verb.
|
||||
//
|
||||
// See also ForVerb.
|
||||
func (x Object) AssertVerb(verbs ...ObjectVerb) bool {
|
||||
verb := ObjectVerb(x.c.GetVerb())
|
||||
|
||||
for i := range verbs {
|
||||
if verbs[i] == verb {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// ForHead binds the ObjectContext to
|
||||
// HEAD operation.
|
||||
func (x *ObjectContext) ForHead() {
|
||||
x.forVerb(session.ObjectVerbHead)
|
||||
// SetExp sets "exp" (expiration time) claim which identifies the expiration time
|
||||
// (in NeoFS epochs) on or after which the Object MUST NOT be accepted for
|
||||
// processing. The processing of the "exp" claim requires that the current
|
||||
// epoch MUST be before the expiration epoch listed in the "exp" claim.
|
||||
//
|
||||
// Naming is inspired by https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.4.
|
||||
//
|
||||
// See also ExpiredAt.
|
||||
func (x *Object) SetExp(exp uint64) {
|
||||
x.lt.SetExp(exp)
|
||||
}
|
||||
|
||||
// IsForHead checks if ObjectContext is bound to
|
||||
// HEAD operation.
|
||||
func (x *ObjectContext) IsForHead() bool {
|
||||
return x.isForVerb(session.ObjectVerbHead)
|
||||
// ExpiredAt asserts "exp" claim.
|
||||
//
|
||||
// Zero Object is expired in any epoch.
|
||||
//
|
||||
// See also SetExp.
|
||||
func (x Object) ExpiredAt(epoch uint64) bool {
|
||||
return x.lt.GetExp() <= epoch
|
||||
}
|
||||
|
||||
// ForSearch binds the ObjectContext to
|
||||
// SEARCH operation.
|
||||
func (x *ObjectContext) ForSearch() {
|
||||
x.forVerb(session.ObjectVerbSearch)
|
||||
// SetNbf sets "nbf" (not before) claim which identifies the time (in NeoFS
|
||||
// epochs) before which the Object MUST NOT be accepted for processing.
|
||||
// The processing of the "nbf" claim requires that the current date/time MUST be
|
||||
// after or equal to the not-before date/time listed in the "nbf" claim.
|
||||
//
|
||||
// Naming is inspired by https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.5.
|
||||
//
|
||||
// See also InvalidAt.
|
||||
func (x *Object) SetNbf(nbf uint64) {
|
||||
x.lt.SetNbf(nbf)
|
||||
}
|
||||
|
||||
// IsForSearch checks if ObjectContext is bound to
|
||||
// SEARCH operation.
|
||||
func (x *ObjectContext) IsForSearch() bool {
|
||||
return x.isForVerb(session.ObjectVerbSearch)
|
||||
// SetIat sets "iat" (issued at) claim which identifies the time (in NeoFS
|
||||
// epochs) at which the Object was issued. This claim can be used to
|
||||
// determine the age of the Object.
|
||||
//
|
||||
// Naming is inspired by https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.6.
|
||||
//
|
||||
// See also InvalidAt.
|
||||
func (x *Object) SetIat(iat uint64) {
|
||||
x.lt.SetIat(iat)
|
||||
}
|
||||
|
||||
// ForRange binds the ObjectContext to
|
||||
// RANGE operation.
|
||||
func (x *ObjectContext) ForRange() {
|
||||
x.forVerb(session.ObjectVerbRange)
|
||||
// InvalidAt asserts "exp", "nbf" and "iat" claims.
|
||||
//
|
||||
// Zero Object is invalid in any epoch.
|
||||
//
|
||||
// See also SetExp, SetNbf, SetIat.
|
||||
func (x Object) InvalidAt(epoch uint64) bool {
|
||||
return x.lt.GetNbf() > epoch || x.lt.GetIat() > epoch || x.ExpiredAt(epoch)
|
||||
}
|
||||
|
||||
// IsForRange checks if ObjectContext is bound to
|
||||
// RANGE operation.
|
||||
func (x *ObjectContext) IsForRange() bool {
|
||||
return x.isForVerb(session.ObjectVerbRange)
|
||||
// SetID sets a unique identifier for the session. The identifier value MUST be
|
||||
// assigned in a manner that ensures that there is a negligible probability
|
||||
// that the same value will be accidentally assigned to a different session.
|
||||
//
|
||||
// ID format MUST be UUID version 4 (random). uuid.New can be used to generate
|
||||
// a new ID. See https://datatracker.ietf.org/doc/html/rfc4122 and
|
||||
// github.com/google/uuid package docs for details.
|
||||
//
|
||||
// See also ID.
|
||||
func (x *Object) SetID(id uuid.UUID) {
|
||||
x.body.SetID(id[:])
|
||||
}
|
||||
|
||||
// ForRangeHash binds the ObjectContext to
|
||||
// RANGEHASH operation.
|
||||
func (x *ObjectContext) ForRangeHash() {
|
||||
x.forVerb(session.ObjectVerbRangeHash)
|
||||
// ID returns a unique identifier for the session.
|
||||
//
|
||||
// Zero Object has empty UUID (all zeros, see uuid.Nil) which is legitimate
|
||||
// but most likely not suitable.
|
||||
//
|
||||
// See also SetID.
|
||||
func (x Object) ID() uuid.UUID {
|
||||
data := x.body.GetID()
|
||||
if data == nil {
|
||||
return uuid.Nil
|
||||
}
|
||||
|
||||
var id uuid.UUID
|
||||
|
||||
err := id.UnmarshalBinary(x.body.GetID())
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("unexpected error from UUID.UnmarshalBinary: %v", err))
|
||||
}
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
// IsForRangeHash checks if ObjectContext is bound to
|
||||
// RANGEHASH operation.
|
||||
func (x *ObjectContext) IsForRangeHash() bool {
|
||||
return x.isForVerb(session.ObjectVerbRangeHash)
|
||||
// SetAuthKey public key corresponding to the private key bound to the session.
|
||||
//
|
||||
// See also AssertAuthKey.
|
||||
func (x *Object) SetAuthKey(key neofscrypto.PublicKey) {
|
||||
bKey := make([]byte, key.MaxEncodedSize())
|
||||
bKey = bKey[:key.Encode(bKey)]
|
||||
|
||||
x.body.SetSessionKey(bKey)
|
||||
}
|
||||
|
||||
// Marshal marshals ObjectContext into a protobuf binary form.
|
||||
func (x *ObjectContext) Marshal() ([]byte, error) {
|
||||
return x.ToV2().StableMarshal(nil)
|
||||
}
|
||||
// AssertAuthKey asserts public key bound to the session.
|
||||
//
|
||||
// Zero Object fails the check.
|
||||
//
|
||||
// See also SetAuthKey.
|
||||
func (x Object) AssertAuthKey(key neofscrypto.PublicKey) bool {
|
||||
bKey := make([]byte, key.MaxEncodedSize())
|
||||
bKey = bKey[:key.Encode(bKey)]
|
||||
|
||||
// Unmarshal unmarshals protobuf binary representation of ObjectContext.
|
||||
func (x *ObjectContext) Unmarshal(data []byte) error {
|
||||
return x.ToV2().Unmarshal(data)
|
||||
}
|
||||
|
||||
// MarshalJSON encodes ObjectContext to protobuf JSON format.
|
||||
func (x *ObjectContext) MarshalJSON() ([]byte, error) {
|
||||
return x.ToV2().MarshalJSON()
|
||||
}
|
||||
|
||||
// UnmarshalJSON decodes ObjectContext from protobuf JSON format.
|
||||
func (x *ObjectContext) UnmarshalJSON(data []byte) error {
|
||||
return x.ToV2().UnmarshalJSON(data)
|
||||
return bytes.Equal(bKey, x.body.GetSessionKey())
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue