Skip to content

Commit 52e083a

Browse files
committed
container: Provide API for attribute management
From nspcc-dev/neofs-api@eaf2cf3. Follow nspcc-dev/neofs-api#362. Signed-off-by: Leonard Lyubich <leonard@morphbits.io>
1 parent 51f382a commit 52e083a

9 files changed

Lines changed: 2221 additions & 224 deletions

File tree

client/container.go

Lines changed: 377 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"errors"
66
"fmt"
7+
"strconv"
78
"time"
89

910
apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status"
@@ -728,3 +729,379 @@ func SyncContainerWithNetwork(ctx context.Context, cnr *container.Container, c N
728729

729730
return nil
730731
}
732+
733+
// SetContainerAttributeParameters groups signed parameters of
734+
// [Client.SetContainerAttribute].
735+
type SetContainerAttributeParameters struct {
736+
ID cid.ID
737+
Attribute string
738+
Value string
739+
}
740+
741+
// GetSignedSetContainerAttributeParameters returns signed message for prm.
742+
func GetSignedSetContainerAttributeParameters(prm SetContainerAttributeParameters) []byte {
743+
return neofsproto.MarshalMessage(&protocontainer.SetAttributeRequest_Body_Parameters{
744+
ContainerId: prm.ID.ProtoMessage(),
745+
Attribute: prm.Attribute,
746+
Value: prm.Value,
747+
})
748+
}
749+
750+
// SignSetContainerAttributeParameters signs given prm.
751+
func SignSetContainerAttributeParameters(signer neofscrypto.Signer, prm SetContainerAttributeParameters) (neofscrypto.Signature, error) {
752+
sig, err := signer.Sign(GetSignedSetContainerAttributeParameters(prm))
753+
if err != nil {
754+
return neofscrypto.Signature{}, err
755+
}
756+
757+
return neofscrypto.NewSignature(signer.Scheme(), signer.Public(), sig), nil
758+
}
759+
760+
// SetContainerAttributeOptions groups optional parameters of [Client.SetContainerAttribute].
761+
type SetContainerAttributeOptions struct {
762+
// TODO: sessionToken *sessionv2.Token
763+
sessionTokenV1 *session.Container
764+
}
765+
766+
// TODO:
767+
// // AttachSessionToken makes client to attach specified session token to the
768+
// // request.
769+
// func (x *SetContainerAttributeOptions) AttachSessionToken(tok sessionv2.Token) {
770+
// x.sessionToken = &tok
771+
// }
772+
773+
// AttachSessionTokenV1 makes client to attach specified session token V1 to the
774+
// request. AttachSessionTokenV1 must not be set together with
775+
// [SetContainerAttributeOptions.AttachSessionToken] that is highly recommended
776+
// to be used instead.
777+
func (x *SetContainerAttributeOptions) AttachSessionTokenV1(tok session.Container) {
778+
x.sessionTokenV1 = &tok
779+
}
780+
781+
// SetContainerAttribute set container attribute.
782+
//
783+
// If container does not have the attribute, it is added. Otherwise, its value
784+
// is swapped.
785+
//
786+
// [SetContainerAttributeParameters.Attribute] must be one of:
787+
// - CORS
788+
// - __NEOFS__LOCK_UNTIL
789+
//
790+
// In general, requirements for [SetContainerAttributeParameters.Value] are the
791+
// same as for container creation. Attribute-specific requirements:
792+
// - __NEOFS__LOCK_UNTIL: new timestamp must be after the current one if any
793+
//
794+
// The prmSig must be a signature of prm in either [neofscrypto.N3] or
795+
// [neofscrypto.ECDSA_DETERMINISTIC_SHA256] scheme (using
796+
// [SignSetContainerAttributeParameters] for example). It must be either
797+
// container owner's or session subject's signature (when using
798+
// [SetContainerAttributeOptions.AttachSessionToken]/[SetContainerAttributeOptions.AttachSessionTokenV1]).
799+
// If session is used, the token must be issued by the container owner and
800+
// include [SetContainerAttributeParameters.ID] +
801+
// [sessionv2.VerbContainerSetAttribute] context.
802+
//
803+
// Operation is async/await. Deadline is determined from ctx. If ctx has no
804+
// deadline, server waits 15s after submitting the transaction. On timeout,
805+
// [apistatus.ErrContainerAwaitTimeout] is returned. This error indicates a
806+
// delay in transaction execution, meaning the operation may still succeed. If
807+
// necessary, caller can continue the wait via [Client.ContainerGet]. It is
808+
// recommended to always use context with timeout. Note that the context
809+
// includes all processing stages incl. network delays.
810+
func (c *Client) SetContainerAttribute(ctx context.Context, prm SetContainerAttributeParameters, prmSig neofscrypto.Signature, opts SetContainerAttributeOptions) error {
811+
var err error
812+
if c.prm.statisticCallback != nil {
813+
startTime := time.Now()
814+
defer func() {
815+
c.sendStatistic(stat.MethodContainerSetAttribute, time.Since(startTime), err)
816+
}()
817+
}
818+
819+
switch {
820+
case prm.ID.IsZero():
821+
err = cid.ErrZero
822+
return err
823+
case prm.Attribute == "":
824+
err = errors.New("missing attribute name")
825+
return err
826+
case prm.Value == "":
827+
err = errors.New("missing attribute value")
828+
return err
829+
// TODO:
830+
// case opts.sessionToken != nil && opts.sessionTokenV1 != nil:
831+
// err = errors.New("both session tokens V1 and V2 set")
832+
// return err
833+
}
834+
835+
switch prm.Attribute {
836+
default:
837+
err = fmt.Errorf("unsupported attribute %s", prm.Attribute)
838+
return err
839+
case "CORS":
840+
case "__NEOFS__LOCK_UNTIL":
841+
if _, parseErr := strconv.ParseInt(prm.Attribute, 10, 64); parseErr != nil {
842+
err = fmt.Errorf("invalid value %s for attribute %s", prm.Value, prm.Attribute)
843+
return err
844+
}
845+
}
846+
847+
// TODO:
848+
// if opts.sessionToken != nil {
849+
// if !opts.sessionToken.HasContainerContext(prm.ID, sessionv2.VerbContainerSetAttribute) {
850+
// err = fmt.Errorf("session token does not have context for %s container and SETATTRIBUTE verb", prm.ID)
851+
// return err
852+
// }
853+
// }
854+
855+
if opts.sessionTokenV1 != nil {
856+
if !opts.sessionTokenV1.AppliedTo(prm.ID) || !opts.sessionTokenV1.AssertVerb(session.VerbContainerSetAttribute) {
857+
err = fmt.Errorf("session token V1 does not have context for %s container and SETATTRIBUTE verb", prm.ID)
858+
return err
859+
}
860+
}
861+
862+
req := &protocontainer.SetAttributeRequest{
863+
Body: &protocontainer.SetAttributeRequest_Body{
864+
Parameters: &protocontainer.SetAttributeRequest_Body_Parameters{
865+
ContainerId: prm.ID.ProtoMessage(),
866+
Attribute: prm.Attribute,
867+
Value: prm.Value,
868+
},
869+
Signature: &refs.SignatureRFC6979{
870+
Key: prmSig.PublicKeyBytes(),
871+
Sign: prmSig.Value(),
872+
},
873+
},
874+
}
875+
// TODO: if opts.sessionToken != nil {
876+
// req.Body.SessionToken = opts.sessionToken.ProtoMessage()
877+
// }
878+
if opts.sessionTokenV1 != nil {
879+
req.Body.SessionTokenV1 = opts.sessionTokenV1.ProtoMessage()
880+
}
881+
882+
buf := c.buffers.Get().(*[]byte)
883+
defer func() { c.buffers.Put(buf) }()
884+
885+
bodyLen := req.BodySignature.MarshaledSize()
886+
if len(*buf) < bodyLen {
887+
b := make([]byte, bodyLen)
888+
buf = &b
889+
}
890+
891+
req.Body.MarshalStable(*buf)
892+
893+
bodySig, err := c.prm.signer.Sign(*buf)
894+
if err != nil {
895+
err = fmt.Errorf("%w: %w", errSignRequest, err)
896+
return err
897+
}
898+
899+
req.BodySignature = &refs.Signature{
900+
Key: neofscrypto.PublicKeyBytes(c.prm.signer.Public()),
901+
Sign: bodySig,
902+
Scheme: refs.SignatureScheme(c.prm.signer.Scheme()),
903+
}
904+
905+
resp, err := c.container.SetAttribute(ctx, req)
906+
if err != nil {
907+
err = rpcErr(err)
908+
return err
909+
}
910+
911+
const bodySignatureField = "body signature"
912+
if resp.BodySignature == nil {
913+
err = newErrMissingResponseField(bodySignatureField)
914+
return err
915+
}
916+
917+
if err = neofscrypto.VerifyMessageSignature(resp.Body, resp.BodySignature, *buf); err != nil {
918+
err = newErrInvalidResponseField(bodySignatureField, err)
919+
return err
920+
}
921+
922+
err = apistatus.ToError(resp.Body.Status)
923+
924+
return err
925+
}
926+
927+
// RemoveContainerAttributeParameters groups signed parameters of
928+
// [Client.RemoveContainerAttribute].
929+
type RemoveContainerAttributeParameters struct {
930+
ID cid.ID
931+
Attribute string
932+
}
933+
934+
// GetSignedRemoveContainerAttributeParameters returns signed message for prm.
935+
func GetSignedRemoveContainerAttributeParameters(prm RemoveContainerAttributeParameters) []byte {
936+
return neofsproto.MarshalMessage(&protocontainer.RemoveAttributeRequest_Body_Parameters{
937+
ContainerId: prm.ID.ProtoMessage(),
938+
Attribute: prm.Attribute,
939+
})
940+
}
941+
942+
// SignRemoveContainerAttributeParameters signs given prm.
943+
func SignRemoveContainerAttributeParameters(signer neofscrypto.Signer, prm RemoveContainerAttributeParameters) (neofscrypto.Signature, error) {
944+
sig, err := signer.Sign(GetSignedRemoveContainerAttributeParameters(prm))
945+
if err != nil {
946+
return neofscrypto.Signature{}, err
947+
}
948+
949+
return neofscrypto.NewSignature(signer.Scheme(), signer.Public(), sig), nil
950+
}
951+
952+
// RemoveContainerAttributeOptions groups optional parameters of [Client.RemoveContainerAttribute].
953+
type RemoveContainerAttributeOptions struct {
954+
// TODO: sessionToken *sessionv2.Token
955+
sessionTokenV1 *session.Container
956+
}
957+
958+
// TODO:
959+
// // AttachSessionToken makes client to attach specified session token to the
960+
// // request.
961+
// func (x *RemoveContainerAttributeOptions) AttachSessionToken(tok sessionv2.Token) {
962+
// x.sessionToken = &tok
963+
// }
964+
965+
// AttachSessionTokenV1 makes client to attach specified session token V1 to the
966+
// request. AttachSessionTokenV1 must not be set together with
967+
// [RemoveContainerAttributeOptions.AttachSessionToken] that is highly recommended
968+
// to be used instead.
969+
func (x *RemoveContainerAttributeOptions) AttachSessionTokenV1(tok session.Container) {
970+
x.sessionTokenV1 = &tok
971+
}
972+
973+
// RemoveContainerAttribute removes container attribute.
974+
//
975+
// If container does not have the attribute, server does nothing and responds
976+
// without an error.
977+
//
978+
// [RemoveContainerAttributeParameters.Attribute] must be one of:
979+
// - CORS
980+
// - __NEOFS__LOCK_UNTIL
981+
//
982+
// Attribute-specific requirements:
983+
// - __NEOFS__LOCK_UNTIL: current timestamp must have already passed if any
984+
//
985+
// The prmSig must be a signature of prm in either [neofscrypto.N3] or
986+
// [neofscrypto.ECDSA_DETERMINISTIC_SHA256] scheme (using
987+
// [SignRemoveContainerAttributeParameters] for example). It must be either
988+
// container owner's or session subject's signature (when using
989+
// [RemoveContainerAttributeOptions.AttachSessionToken]/[RemoveContainerAttributeOptions.AttachSessionTokenV1]).
990+
// If session is used, the token must be issued by the container owner and
991+
// include [RemoveContainerAttributeParameters.ID] +
992+
// [sessionv2.VerbContainerRemoveAttribute] context.
993+
//
994+
// Operation is async/await. Deadline is determined from ctx. If ctx has no
995+
// deadline, server waits 15s after submitting the transaction. On timeout,
996+
// [apistatus.ErrContainerAwaitTimeout] is returned. This error indicates a
997+
// delay in transaction execution, meaning the operation may still succeed. If
998+
// necessary, caller can continue the wait via [Client.ContainerGet]. It is
999+
// recommended to always use context with timeout. Note that the context
1000+
// includes all processing stages incl. network delays.
1001+
func (c *Client) RemoveContainerAttribute(ctx context.Context, prm RemoveContainerAttributeParameters, prmSig neofscrypto.Signature, opts RemoveContainerAttributeOptions) error {
1002+
var err error
1003+
if c.prm.statisticCallback != nil {
1004+
startTime := time.Now()
1005+
defer func() {
1006+
c.sendStatistic(stat.MethodContainerRemoveAttribute, time.Since(startTime), err)
1007+
}()
1008+
}
1009+
1010+
switch {
1011+
case prm.ID.IsZero():
1012+
err = cid.ErrZero
1013+
return err
1014+
case prm.Attribute == "":
1015+
err = errors.New("missing attribute name")
1016+
return err
1017+
// TODO:
1018+
// case opts.sessionToken != nil && opts.sessionTokenV1 != nil:
1019+
// err = errors.New("both session tokens V1 and V2 set")
1020+
// return err
1021+
}
1022+
1023+
switch prm.Attribute {
1024+
default:
1025+
err = fmt.Errorf("unsupported attribute %s", prm.Attribute)
1026+
return err
1027+
case "CORS", "__NEOFS__LOCK_UNTIL":
1028+
}
1029+
1030+
// TODO:
1031+
// if opts.sessionToken != nil {
1032+
// if !opts.sessionToken.HasContainerContext(prm.ID, sessionv2.VerbContainerRemoveAttribute) {
1033+
// err = fmt.Errorf("session token does not have context for %s container and REMOVEATTRIBUTE verb", prm.ID)
1034+
// return err
1035+
// }
1036+
// }
1037+
1038+
if opts.sessionTokenV1 != nil {
1039+
if !opts.sessionTokenV1.AppliedTo(prm.ID) || !opts.sessionTokenV1.AssertVerb(session.VerbContainerRemoveAttribute) {
1040+
err = fmt.Errorf("session token V1 does not have context for %s container and REMOVEATTRIBUTE verb", prm.ID)
1041+
return err
1042+
}
1043+
}
1044+
1045+
req := &protocontainer.RemoveAttributeRequest{
1046+
Body: &protocontainer.RemoveAttributeRequest_Body{
1047+
Parameters: &protocontainer.RemoveAttributeRequest_Body_Parameters{
1048+
ContainerId: prm.ID.ProtoMessage(),
1049+
Attribute: prm.Attribute,
1050+
},
1051+
Signature: &refs.SignatureRFC6979{
1052+
Key: prmSig.PublicKeyBytes(),
1053+
Sign: prmSig.Value(),
1054+
},
1055+
},
1056+
}
1057+
// TODO: if opts.sessionToken != nil {
1058+
// req.Body.SessionToken = opts.sessionToken.ProtoMessage()
1059+
// }
1060+
if opts.sessionTokenV1 != nil {
1061+
req.Body.SessionTokenV1 = opts.sessionTokenV1.ProtoMessage()
1062+
}
1063+
1064+
buf := c.buffers.Get().(*[]byte)
1065+
defer func() { c.buffers.Put(buf) }()
1066+
1067+
bodyLen := req.BodySignature.MarshaledSize()
1068+
if len(*buf) < bodyLen {
1069+
b := make([]byte, bodyLen)
1070+
buf = &b
1071+
}
1072+
1073+
req.Body.MarshalStable(*buf)
1074+
1075+
bodySig, err := c.prm.signer.Sign(*buf)
1076+
if err != nil {
1077+
err = fmt.Errorf("%w: %w", errSignRequest, err)
1078+
return err
1079+
}
1080+
1081+
req.BodySignature = &refs.Signature{
1082+
Key: neofscrypto.PublicKeyBytes(c.prm.signer.Public()),
1083+
Sign: bodySig,
1084+
Scheme: refs.SignatureScheme(c.prm.signer.Scheme()),
1085+
}
1086+
1087+
resp, err := c.container.RemoveAttribute(ctx, req)
1088+
if err != nil {
1089+
err = rpcErr(err)
1090+
return err
1091+
}
1092+
1093+
const bodySignatureField = "body signature"
1094+
if resp.BodySignature == nil {
1095+
err = newErrMissingResponseField(bodySignatureField)
1096+
return err
1097+
}
1098+
1099+
if err = neofscrypto.VerifyMessageSignature(resp.Body, resp.BodySignature, *buf); err != nil {
1100+
err = newErrInvalidResponseField(bodySignatureField, err)
1101+
return err
1102+
}
1103+
1104+
err = apistatus.ToError(resp.Body.Status)
1105+
1106+
return err
1107+
}

crypto/proto.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,11 @@ func verifyMessageSignatureN3(m ProtoMessage, s *refs.Signature, b []byte, verif
306306
return verifyMessageSignature(m, s, b)
307307
}
308308

309+
// VerifyMessageSignature verifies signature of m using buffer b.
310+
func VerifyMessageSignature(m ProtoMessage, s *refs.Signature, b []byte) error {
311+
return verifyMessageSignature(m, s, b)
312+
}
313+
309314
func verifyMessageSignature(m ProtoMessage, s *refs.Signature, b []byte) error {
310315
if len(s.Key) == 0 {
311316
return errors.New("missing public key")

0 commit comments

Comments
 (0)