|
4 | 4 | "context" |
5 | 5 | "errors" |
6 | 6 | "fmt" |
| 7 | + "strconv" |
7 | 8 | "time" |
8 | 9 |
|
9 | 10 | apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status" |
@@ -728,3 +729,379 @@ func SyncContainerWithNetwork(ctx context.Context, cnr *container.Container, c N |
728 | 729 |
|
729 | 730 | return nil |
730 | 731 | } |
| 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 | +} |
0 commit comments