From 27cd7214225b9507ebdeefffb5645ccb89e12b90 Mon Sep 17 00:00:00 2001 From: Alex Vanin Date: Thu, 24 Mar 2022 11:05:41 +0300 Subject: [PATCH] [#170] bearer: Add docs, refactor Signed-off-by: Alex Vanin --- bearer/bearer.go | 316 ++++++++++++++++++++++++++++++++++++++++ bearer/bearer_test.go | 57 ++++++++ bearer/doc.go | 28 ++++ bearer/test/doc.go | 6 + bearer/test/generate.go | 20 +++ client/object_delete.go | 9 +- client/object_get.go | 11 +- client/object_hash.go | 9 +- client/object_put.go | 9 +- client/object_search.go | 11 +- pool/pool.go | 8 +- token/bearer.go | 226 ---------------------------- token/bearer_test.go | 81 ---------- token/test/generate.go | 40 ----- 14 files changed, 463 insertions(+), 368 deletions(-) create mode 100644 bearer/bearer.go create mode 100644 bearer/bearer_test.go create mode 100644 bearer/doc.go create mode 100644 bearer/test/doc.go create mode 100644 bearer/test/generate.go delete mode 100644 token/bearer.go delete mode 100644 token/bearer_test.go delete mode 100644 token/test/generate.go diff --git a/bearer/bearer.go b/bearer/bearer.go new file mode 100644 index 00000000..6eb4a7a7 --- /dev/null +++ b/bearer/bearer.go @@ -0,0 +1,316 @@ +package bearer + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "errors" + + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/nspcc-dev/neofs-api-go/v2/acl" + v2signature "github.com/nspcc-dev/neofs-api-go/v2/signature" + "github.com/nspcc-dev/neofs-sdk-go/eacl" + "github.com/nspcc-dev/neofs-sdk-go/owner" + "github.com/nspcc-dev/neofs-sdk-go/signature" + sigutil "github.com/nspcc-dev/neofs-sdk-go/util/signature" +) + +var ( + errNilBearerTokenBody = errors.New("bearer token body is not set") + errNilBearerTokenEACL = errors.New("bearer token ContainerEACL table is not set") +) + +// Token represents bearer token for object service operations. +// +// Token is mutually compatible with github.com/nspcc-dev/neofs-api-go/v2/acl.BearerToken +// message. See ReadFromV2 / WriteToV2 methods. +// +// Instances can be created using built-in var declaration. +// +// Note that direct typecast is not safe and may result in loss of compatibility: +// _ = Token(acl.BearerToken{}) // not recommended +type Token acl.BearerToken + +// ReadFromV2 reads Token from the acl.BearerToken message. +// +// See also WriteToV2. +func (b *Token) ReadFromV2(m acl.BearerToken) { + *b = Token(m) +} + +// WriteToV2 writes Token to the acl.BearerToken message. +// The message must not be nil. +// +// See also ReadFromV2. +func (b Token) WriteToV2(m *acl.BearerToken) { + *m = (acl.BearerToken)(b) +} + +// IsEmpty returns true if bearer token has no fields set. +func (b Token) IsEmpty() bool { + v2token := (acl.BearerToken)(b) + return v2token.GetBody() == nil && v2token.GetSignature() == nil +} + +// SetExpiration sets "exp" (expiration time) claim which identifies the +// expiration time (in NeoFS epochs) on or after which the Token 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 Expiration. +func (b *Token) SetExpiration(exp uint64) { + v2token := (*acl.BearerToken)(b) + + body := v2token.GetBody() + if body == nil { + body = new(acl.BearerTokenBody) + } + + lt := new(acl.TokenLifetime) + lt.SetExp(exp) + lt.SetNbf(body.GetLifetime().GetNbf()) + lt.SetIat(body.GetLifetime().GetIat()) + + body.SetLifetime(lt) + v2token.SetBody(body) +} + +// Expiration returns "exp" claim. +// +// Empty Token has zero "exp". +// +// See also SetExpiration. +func (b Token) Expiration() uint64 { + v2token := (acl.BearerToken)(b) + return v2token.GetBody().GetLifetime().GetExp() +} + +// SetNotBefore sets "nbf" (not before) claim which identifies the time (in +// NeoFS epochs) before which the Token MUST NOT be accepted for processing. The +// processing of the "nbf" claim requires that the current epoch MUST be +// after or equal to the not-before epoch listed in the "nbf" claim. +// +// Naming is inspired by https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.5. +// +// See also NotBefore. +func (b *Token) SetNotBefore(nbf uint64) { + v2token := (*acl.BearerToken)(b) + + body := v2token.GetBody() + if body == nil { + body = new(acl.BearerTokenBody) + } + + lt := new(acl.TokenLifetime) + lt.SetExp(body.GetLifetime().GetExp()) + lt.SetNbf(nbf) + lt.SetIat(body.GetLifetime().GetIat()) + + body.SetLifetime(lt) + v2token.SetBody(body) +} + +// NotBefore returns "nbf" claim. +// +// Empty Token has zero "nbf". +// +// See also SetNotBefore. +func (b Token) NotBefore() uint64 { + v2token := (acl.BearerToken)(b) + return v2token.GetBody().GetLifetime().GetNbf() +} + +// SetIssuedAt sets "iat" (issued at) claim which identifies the time (in NeoFS +// epochs) at which the Token was issued. This claim can be used to determine +// the age of the Token. +// +// Naming is inspired by https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.6. +// +// See also IssuedAt. +func (b *Token) SetIssuedAt(iat uint64) { + v2token := (*acl.BearerToken)(b) + + body := v2token.GetBody() + if body == nil { + body = new(acl.BearerTokenBody) + } + + lt := new(acl.TokenLifetime) + lt.SetExp(body.GetLifetime().GetExp()) + lt.SetNbf(body.GetLifetime().GetNbf()) + lt.SetIat(iat) + + body.SetLifetime(lt) + v2token.SetBody(body) +} + +// IssuedAt returns "iat" claim. +// +// Empty Token has zero "iat". +// +// See also SetIssuedAt. +func (b Token) IssuedAt() uint64 { + v2token := (acl.BearerToken)(b) + return v2token.GetBody().GetLifetime().GetIat() +} + +// SetEACLTable sets extended ACL table that should be used during object +// service request processing with bearer token. +// +// See also EACLTable. +func (b *Token) SetEACLTable(table eacl.Table) { + v2 := (*acl.BearerToken)(b) + + body := v2.GetBody() + if body == nil { + body = new(acl.BearerTokenBody) + } + + body.SetEACL(table.ToV2()) + v2.SetBody(body) +} + +// EACLTable returns extended ACL table that should be used during object +// service request processing with bearer token. +// +// See also SetEACLTable. +func (b Token) EACLTable() eacl.Table { + v2 := (acl.BearerToken)(b) + return *eacl.NewTableFromV2(v2.GetBody().GetEACL()) +} + +// SetOwnerID sets owner.ID value of the user who can attach bearer token to +// its requests. +// +// See also OwnerID. +func (b *Token) SetOwnerID(id owner.ID) { + v2 := (*acl.BearerToken)(b) + + body := v2.GetBody() + if body == nil { + body = new(acl.BearerTokenBody) + } + + body.SetOwnerID(id.ToV2()) + v2.SetBody(body) +} + +// OwnerID returns owner.ID value of the user who can attach bearer token to +// its requests. +// +// See also SetOwnerID. +func (b Token) OwnerID() owner.ID { + v2 := (acl.BearerToken)(b) + return *owner.NewIDFromV2(v2.GetBody().GetOwnerID()) +} + +// Sign signs bearer token. This method should be invoked with the private +// key of container owner to allow overriding extended ACL table of the container +// included in this token. +// +// See also Signature. +func (b *Token) Sign(key ecdsa.PrivateKey) error { + err := sanityCheck(b) + if err != nil { + return err + } + + v2 := (*acl.BearerToken)(b) + signWrapper := v2signature.StableMarshalerWrapper{SM: v2.GetBody()} + + sig, err := sigutil.SignData(&key, signWrapper) + if err != nil { + return err + } + + v2.SetSignature(sig.ToV2()) + + return nil +} + +// VerifySignature returns nil if bearer token contains correct signature. +func (b Token) VerifySignature() error { + if b.IsEmpty() { + return nil + } + + v2 := (acl.BearerToken)(b) + + return sigutil.VerifyData( + v2signature.StableMarshalerWrapper{SM: v2.GetBody()}, + signature.NewFromV2(v2.GetSignature())) +} + +// Issuer returns owner.ID associated with the key that signed bearer token. +// To pass node validation it should be owner of requested container. +// +// If token is not signed, issuer returns empty owner ID. +// +// See also Sign. +func (b Token) Issuer() (id owner.ID) { + v2 := (acl.BearerToken)(b) + + pub, _ := keys.NewPublicKeyFromBytes(v2.GetSignature().GetKey(), elliptic.P256()) + if pub == nil { + return id + } + + return *owner.NewIDFromPublicKey((*ecdsa.PublicKey)(pub)) +} + +// sanityCheck if bearer token is ready to be issued. +func sanityCheck(b *Token) error { + v2 := (*acl.BearerToken)(b) + + switch { + case v2.GetBody() == nil: + return errNilBearerTokenBody + case v2.GetBody().GetEACL() == nil: + return errNilBearerTokenEACL + } + + // consider checking ContainerEACL sanity there, lifetime correctness, etc. + + return nil +} + +// Marshal marshals Token into a canonical NeoFS binary format (proto3 +// with direct field order). +// +// See also Unmarshal. +func (b Token) Marshal() []byte { + v2 := (acl.BearerToken)(b) + + data, err := v2.StableMarshal(nil) + if err != nil { + panic(err) + } + + return data +} + +// Unmarshal unmarshals Token from canonical NeoFS binary format (proto3 +// with direct field order). +// +// See also Marshal. +func (b *Token) Unmarshal(data []byte) error { + v2 := (*acl.BearerToken)(b) + return v2.Unmarshal(data) +} + +// MarshalJSON encodes Token to protobuf JSON format. +// +// See also UnmarshalJSON. +func (b Token) MarshalJSON() ([]byte, error) { + v2 := (acl.BearerToken)(b) + return v2.MarshalJSON() +} + +// UnmarshalJSON decodes Token from protobuf JSON format. +// +// See also MarshalJSON. +func (b *Token) UnmarshalJSON(data []byte) error { + v2 := (*acl.BearerToken)(b) + return v2.UnmarshalJSON(data) +} diff --git a/bearer/bearer_test.go b/bearer/bearer_test.go new file mode 100644 index 00000000..0316187b --- /dev/null +++ b/bearer/bearer_test.go @@ -0,0 +1,57 @@ +package bearer_test + +import ( + "crypto/ecdsa" + "testing" + + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/nspcc-dev/neofs-sdk-go/bearer" + tokentest "github.com/nspcc-dev/neofs-sdk-go/bearer/test" + "github.com/nspcc-dev/neofs-sdk-go/eacl" + "github.com/nspcc-dev/neofs-sdk-go/owner" + "github.com/stretchr/testify/require" +) + +func TestBearerToken_Issuer(t *testing.T) { + var bearerToken bearer.Token + + t.Run("non signed token", func(t *testing.T) { + id := bearerToken.Issuer() + require.Equal(t, owner.ID{}, id) + }) + + t.Run("signed token", func(t *testing.T) { + p, err := keys.NewPrivateKey() + require.NoError(t, err) + + ownerID := owner.NewIDFromPublicKey((*ecdsa.PublicKey)(p.PublicKey())) + + bearerToken.SetEACLTable(*eacl.NewTable()) + require.NoError(t, bearerToken.Sign(p.PrivateKey)) + issuer := bearerToken.Issuer() + require.True(t, ownerID.Equal(&issuer)) + }) +} + +func TestFilterEncoding(t *testing.T) { + f := tokentest.Token() + + t.Run("binary", func(t *testing.T) { + data := f.Marshal() + + var f2 bearer.Token + require.NoError(t, f2.Unmarshal(data)) + + require.Equal(t, f, f2) + }) + + t.Run("json", func(t *testing.T) { + data, err := f.MarshalJSON() + require.NoError(t, err) + + var d2 bearer.Token + require.NoError(t, d2.UnmarshalJSON(data)) + + require.Equal(t, f, d2) + }) +} diff --git a/bearer/doc.go b/bearer/doc.go new file mode 100644 index 00000000..3888dbe2 --- /dev/null +++ b/bearer/doc.go @@ -0,0 +1,28 @@ +/* +Package bearer provides bearer token definition. + +Bearer token is attached to the object service requests, and it overwrites +extended ACL of the container. Mainly it is used to provide access of private +data for specific user. Therefore, it must be signed by owner of the container. + +Define bearer token by setting correct lifetime, extended ACL and owner ID of +the user that will attach token to its requests. + var bearerToken bearer.Token + bearerToken.SetExpiration(500) + bearerToken.SetIssuedAt(10) + bearerToken.SetNotBefore(10) + bearerToken.SetEACL(eaclTable) + bearerToken.SetOwner(ownerID) + +Bearer token must be signed by owner of the container. + err := bearerToken.Sign(privateKey) + +Provide signed token in JSON or binary format to the request sender. Request +sender can attach this bearer token to the object service requests: + import sdkClient "github.com/nspcc-dev/neofs-sdk-go/client" + + var headParams sdkClient.PrmObjectHead + headParams.WithBearerToken(bearerToken) + response, err := client.ObjectHead(ctx, headParams) +*/ +package bearer diff --git a/bearer/test/doc.go b/bearer/test/doc.go new file mode 100644 index 00000000..ab4682f3 --- /dev/null +++ b/bearer/test/doc.go @@ -0,0 +1,6 @@ +/* +Package bearertest provides functions for testing bearer package. + +Note that this package intended only for tests. +*/ +package bearertest diff --git a/bearer/test/generate.go b/bearer/test/generate.go new file mode 100644 index 00000000..5f83258e --- /dev/null +++ b/bearer/test/generate.go @@ -0,0 +1,20 @@ +package bearertest + +import ( + "github.com/nspcc-dev/neofs-sdk-go/bearer" + eacltest "github.com/nspcc-dev/neofs-sdk-go/eacl/test" + ownertest "github.com/nspcc-dev/neofs-sdk-go/owner/test" +) + +// Token returns random bearer.Token. +// +// Resulting token is unsigned. +func Token() (t bearer.Token) { + t.SetExpiration(3) + t.SetNotBefore(2) + t.SetIssuedAt(1) + t.SetOwnerID(*ownertest.ID()) + t.SetEACLTable(*eacltest.Table()) + + return t +} diff --git a/client/object_delete.go b/client/object_delete.go index 9b27895e..08779b17 100644 --- a/client/object_delete.go +++ b/client/object_delete.go @@ -4,15 +4,16 @@ import ( "context" "crypto/ecdsa" + "github.com/nspcc-dev/neofs-api-go/v2/acl" v2object "github.com/nspcc-dev/neofs-api-go/v2/object" v2refs "github.com/nspcc-dev/neofs-api-go/v2/refs" rpcapi "github.com/nspcc-dev/neofs-api-go/v2/rpc" "github.com/nspcc-dev/neofs-api-go/v2/rpc/client" v2session "github.com/nspcc-dev/neofs-api-go/v2/session" + "github.com/nspcc-dev/neofs-sdk-go/bearer" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" oid "github.com/nspcc-dev/neofs-sdk-go/object/id" "github.com/nspcc-dev/neofs-sdk-go/session" - "github.com/nspcc-dev/neofs-sdk-go/token" ) // PrmObjectDelete groups parameters of ObjectDelete operation. @@ -42,8 +43,10 @@ func (x *PrmObjectDelete) WithinSession(t session.Token) { // If set, underlying eACL rules will be used in access control. // // Must be signed. -func (x *PrmObjectDelete) WithBearerToken(t token.BearerToken) { - x.meta.SetBearerToken(t.ToV2()) +func (x *PrmObjectDelete) WithBearerToken(t bearer.Token) { + var v2token acl.BearerToken + t.WriteToV2(&v2token) + x.meta.SetBearerToken(&v2token) } // FromContainer specifies NeoFS container of the object. diff --git a/client/object_get.go b/client/object_get.go index 2fc05424..2f1221fa 100644 --- a/client/object_get.go +++ b/client/object_get.go @@ -7,17 +7,18 @@ import ( "fmt" "io" + "github.com/nspcc-dev/neofs-api-go/v2/acl" v2object "github.com/nspcc-dev/neofs-api-go/v2/object" v2refs "github.com/nspcc-dev/neofs-api-go/v2/refs" rpcapi "github.com/nspcc-dev/neofs-api-go/v2/rpc" "github.com/nspcc-dev/neofs-api-go/v2/rpc/client" v2session "github.com/nspcc-dev/neofs-api-go/v2/session" + "github.com/nspcc-dev/neofs-sdk-go/bearer" apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" "github.com/nspcc-dev/neofs-sdk-go/object" oid "github.com/nspcc-dev/neofs-sdk-go/object/id" "github.com/nspcc-dev/neofs-sdk-go/session" - "github.com/nspcc-dev/neofs-sdk-go/token" ) // shared parameters of GET/HEAD/RANGE. @@ -32,7 +33,7 @@ type prmObjectRead struct { session session.Token bearerSet bool - bearer token.BearerToken + bearer bearer.Token cnrSet bool cnr cid.ID @@ -47,7 +48,9 @@ func (x prmObjectRead) writeToMetaHeader(h *v2session.RequestMetaHeader) { } if x.bearerSet { - h.SetBearerToken(x.bearer.ToV2()) + var v2token acl.BearerToken + x.bearer.WriteToV2(&v2token) + h.SetBearerToken(&v2token) } if x.sessionSet { @@ -83,7 +86,7 @@ func (x *prmObjectRead) WithinSession(t session.Token) { // If set, underlying eACL rules will be used in access control. // // Must be signed. -func (x *prmObjectRead) WithBearerToken(t token.BearerToken) { +func (x *prmObjectRead) WithBearerToken(t bearer.Token) { x.bearer = t x.bearerSet = true } diff --git a/client/object_hash.go b/client/object_hash.go index a0d5b9c4..c4f86c97 100644 --- a/client/object_hash.go +++ b/client/object_hash.go @@ -3,15 +3,16 @@ package client import ( "context" + "github.com/nspcc-dev/neofs-api-go/v2/acl" v2object "github.com/nspcc-dev/neofs-api-go/v2/object" v2refs "github.com/nspcc-dev/neofs-api-go/v2/refs" rpcapi "github.com/nspcc-dev/neofs-api-go/v2/rpc" "github.com/nspcc-dev/neofs-api-go/v2/rpc/client" v2session "github.com/nspcc-dev/neofs-api-go/v2/session" + "github.com/nspcc-dev/neofs-sdk-go/bearer" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" oid "github.com/nspcc-dev/neofs-sdk-go/object/id" "github.com/nspcc-dev/neofs-sdk-go/session" - "github.com/nspcc-dev/neofs-sdk-go/token" ) // PrmObjectHash groups parameters of ObjectHash operation. @@ -45,8 +46,10 @@ func (x *PrmObjectHash) WithinSession(t session.Token) { // If set, underlying eACL rules will be used in access control. // // Must be signed. -func (x *PrmObjectHash) WithBearerToken(t token.BearerToken) { - x.meta.SetBearerToken(t.ToV2()) +func (x *PrmObjectHash) WithBearerToken(t bearer.Token) { + var v2token acl.BearerToken + t.WriteToV2(&v2token) + x.meta.SetBearerToken(&v2token) } // FromContainer specifies NeoFS container of the object. diff --git a/client/object_put.go b/client/object_put.go index 3bdae0df..1c0e4ca9 100644 --- a/client/object_put.go +++ b/client/object_put.go @@ -5,14 +5,15 @@ import ( "crypto/ecdsa" "fmt" + "github.com/nspcc-dev/neofs-api-go/v2/acl" v2object "github.com/nspcc-dev/neofs-api-go/v2/object" rpcapi "github.com/nspcc-dev/neofs-api-go/v2/rpc" "github.com/nspcc-dev/neofs-api-go/v2/rpc/client" v2session "github.com/nspcc-dev/neofs-api-go/v2/session" + "github.com/nspcc-dev/neofs-sdk-go/bearer" "github.com/nspcc-dev/neofs-sdk-go/object" oid "github.com/nspcc-dev/neofs-sdk-go/object/id" "github.com/nspcc-dev/neofs-sdk-go/session" - "github.com/nspcc-dev/neofs-sdk-go/token" ) // PrmObjectPutInit groups parameters of ObjectPutInit operation. @@ -69,8 +70,10 @@ func (x *ObjectWriter) UseKey(key ecdsa.PrivateKey) { // WithBearerToken attaches bearer token to be used for the operation. // Should be called once before any writing steps. -func (x *ObjectWriter) WithBearerToken(t token.BearerToken) { - x.metaHdr.SetBearerToken(t.ToV2()) +func (x *ObjectWriter) WithBearerToken(t bearer.Token) { + var v2token acl.BearerToken + t.WriteToV2(&v2token) + x.metaHdr.SetBearerToken(&v2token) } // WithinSession specifies session within which object should be stored. diff --git a/client/object_search.go b/client/object_search.go index 38f63994..a9ee9297 100644 --- a/client/object_search.go +++ b/client/object_search.go @@ -7,17 +7,18 @@ import ( "fmt" "io" + "github.com/nspcc-dev/neofs-api-go/v2/acl" v2object "github.com/nspcc-dev/neofs-api-go/v2/object" v2refs "github.com/nspcc-dev/neofs-api-go/v2/refs" rpcapi "github.com/nspcc-dev/neofs-api-go/v2/rpc" "github.com/nspcc-dev/neofs-api-go/v2/rpc/client" v2session "github.com/nspcc-dev/neofs-api-go/v2/session" + "github.com/nspcc-dev/neofs-sdk-go/bearer" apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" "github.com/nspcc-dev/neofs-sdk-go/object" oid "github.com/nspcc-dev/neofs-sdk-go/object/id" "github.com/nspcc-dev/neofs-sdk-go/session" - "github.com/nspcc-dev/neofs-sdk-go/token" ) // PrmObjectSearch groups parameters of ObjectSearch operation. @@ -30,7 +31,7 @@ type PrmObjectSearch struct { session session.Token bearerSet bool - bearer token.BearerToken + bearer bearer.Token cnrSet bool cnr cid.ID @@ -59,7 +60,7 @@ func (x *PrmObjectSearch) WithinSession(t session.Token) { // If set, underlying eACL rules will be used in access control. // // Must be signed. -func (x *PrmObjectSearch) WithBearerToken(t token.BearerToken) { +func (x *PrmObjectSearch) WithBearerToken(t bearer.Token) { x.bearer = t x.bearerSet = true } @@ -257,7 +258,9 @@ func (c *Client) ObjectSearchInit(ctx context.Context, prm PrmObjectSearch) (*Ob } if prm.bearerSet { - meta.SetBearerToken(prm.bearer.ToV2()) + var v2token acl.BearerToken + prm.bearer.WriteToV2(&v2token) + meta.SetBearerToken(&v2token) } if prm.sessionSet { diff --git a/pool/pool.go b/pool/pool.go index b6791db4..54d9462e 100644 --- a/pool/pool.go +++ b/pool/pool.go @@ -17,6 +17,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/crypto/keys" sessionv2 "github.com/nspcc-dev/neofs-api-go/v2/session" "github.com/nspcc-dev/neofs-sdk-go/accounting" + "github.com/nspcc-dev/neofs-sdk-go/bearer" sdkClient "github.com/nspcc-dev/neofs-sdk-go/client" "github.com/nspcc-dev/neofs-sdk-go/container" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" @@ -27,7 +28,6 @@ import ( oid "github.com/nspcc-dev/neofs-sdk-go/object/id" "github.com/nspcc-dev/neofs-sdk-go/owner" "github.com/nspcc-dev/neofs-sdk-go/session" - "github.com/nspcc-dev/neofs-sdk-go/token" "go.uber.org/zap" ) @@ -620,7 +620,7 @@ func (x *prmContext) useVerb(verb sessionv2.ObjectSessionVerb) { type prmCommon struct { key *ecdsa.PrivateKey - btoken *token.BearerToken + btoken *bearer.Token stoken *session.Token } @@ -631,8 +631,8 @@ func (x *prmCommon) UseKey(key *ecdsa.PrivateKey) { } // UseBearer attaches bearer token to be used for the operation. -func (x *prmCommon) UseBearer(token *token.BearerToken) { - x.btoken = token +func (x *prmCommon) UseBearer(token bearer.Token) { + x.btoken = &token } // UseSession specifies session within which operation should be performed. diff --git a/token/bearer.go b/token/bearer.go deleted file mode 100644 index 3cd30884..00000000 --- a/token/bearer.go +++ /dev/null @@ -1,226 +0,0 @@ -package token - -import ( - "crypto/ecdsa" - "crypto/elliptic" - "errors" - - "github.com/nspcc-dev/neo-go/pkg/crypto/keys" - "github.com/nspcc-dev/neofs-api-go/v2/acl" - v2signature "github.com/nspcc-dev/neofs-api-go/v2/signature" - "github.com/nspcc-dev/neofs-sdk-go/eacl" - "github.com/nspcc-dev/neofs-sdk-go/owner" - "github.com/nspcc-dev/neofs-sdk-go/signature" - sigutil "github.com/nspcc-dev/neofs-sdk-go/util/signature" -) - -var ( - errNilBearerToken = errors.New("bearer token is not set") - errNilBearerTokenBody = errors.New("bearer token body is not set") - errNilBearerTokenEACL = errors.New("bearer token ContainerEACL table is not set") -) - -type BearerToken struct { - token acl.BearerToken -} - -// ToV2 converts BearerToken to v2 BearerToken message. -// -// Nil BearerToken converts to nil. -func (b *BearerToken) ToV2() *acl.BearerToken { - if b == nil { - return nil - } - - return &b.token -} - -func (b *BearerToken) Empty() bool { - return b == nil || b.token.GetBody() == nil && b.token.GetSignature() == nil -} - -func (b *BearerToken) SetLifetime(exp, nbf, iat uint64) { - body := b.token.GetBody() - if body == nil { - body = new(acl.BearerTokenBody) - } - - lt := new(acl.TokenLifetime) - lt.SetExp(exp) - lt.SetNbf(nbf) - lt.SetIat(iat) - - body.SetLifetime(lt) - b.token.SetBody(body) -} - -func (b BearerToken) Expiration() uint64 { - return b.token.GetBody().GetLifetime().GetExp() -} - -func (b BearerToken) NotBeforeTime() uint64 { - return b.token.GetBody().GetLifetime().GetNbf() -} - -func (b BearerToken) IssuedAt() uint64 { - return b.token.GetBody().GetLifetime().GetIat() -} - -func (b *BearerToken) SetEACLTable(table *eacl.Table) { - body := b.token.GetBody() - if body == nil { - body = new(acl.BearerTokenBody) - } - - body.SetEACL(table.ToV2()) - b.token.SetBody(body) -} - -func (b BearerToken) EACLTable() *eacl.Table { - return eacl.NewTableFromV2(b.token.GetBody().GetEACL()) -} - -func (b *BearerToken) SetOwner(id *owner.ID) { - body := b.token.GetBody() - if body == nil { - body = new(acl.BearerTokenBody) - } - - body.SetOwnerID(id.ToV2()) - b.token.SetBody(body) -} - -func (b BearerToken) OwnerID() *owner.ID { - return owner.NewIDFromV2(b.token.GetBody().GetOwnerID()) -} - -func (b *BearerToken) SignToken(key *ecdsa.PrivateKey) error { - err := sanityCheck(b) - if err != nil { - return err - } - - signWrapper := v2signature.StableMarshalerWrapper{SM: b.token.GetBody()} - - sig, err := sigutil.SignData(key, signWrapper) - if err != nil { - return err - } - - b.token.SetSignature(sig.ToV2()) - return nil -} - -func (b BearerToken) Signature() *signature.Signature { - return signature.NewFromV2(b.token.GetSignature()) -} - -func (b BearerToken) VerifySignature() error { - if b.Empty() { - return nil - } - - sigV2 := b.token.GetSignature() - return sigutil.VerifyData( - v2signature.StableMarshalerWrapper{SM: b.token.GetBody()}, - signature.NewFromV2(sigV2)) -} - -// Issuer returns owner.ID associated with the key that signed bearer token. -// To pass node validation it should be owner of requested container. Returns -// nil if token is not signed. -func (b *BearerToken) Issuer() *owner.ID { - pub, _ := keys.NewPublicKeyFromBytes(b.token.GetSignature().GetKey(), elliptic.P256()) - if pub == nil { - return nil - } - return owner.NewIDFromPublicKey((*ecdsa.PublicKey)(pub)) -} - -// NewBearerToken creates and initializes blank BearerToken. -// -// Defaults: -// - signature: nil; -// - eacl: nil; -// - ownerID: nil; -// - exp: 0; -// - nbf: 0; -// - iat: 0. -func NewBearerToken() *BearerToken { - b := new(BearerToken) - b.token = acl.BearerToken{} - b.token.SetBody(new(acl.BearerTokenBody)) - - return b -} - -// ToV2 converts BearerToken to v2 BearerToken message. -func NewBearerTokenFromV2(v2 *acl.BearerToken) *BearerToken { - if v2 == nil { - v2 = new(acl.BearerToken) - } - - return &BearerToken{ - token: *v2, - } -} - -// sanityCheck if bearer token is ready to be issued. -func sanityCheck(b *BearerToken) error { - switch { - case b == nil: - return errNilBearerToken - case b.token.GetBody() == nil: - return errNilBearerTokenBody - case b.token.GetBody().GetEACL() == nil: - return errNilBearerTokenEACL - } - - // consider checking ContainerEACL sanity there, lifetime correctness, etc. - - return nil -} - -// Marshal marshals BearerToken into a protobuf binary form. -// -// Buffer is allocated when the argument is empty. -// Otherwise, the first buffer is used. -func (b *BearerToken) Marshal(bs ...[]byte) ([]byte, error) { - var buf []byte - if len(bs) > 0 { - buf = bs[0] - } - - return b.ToV2(). - StableMarshal(buf) -} - -// Unmarshal unmarshals protobuf binary representation of BearerToken. -func (b *BearerToken) Unmarshal(data []byte) error { - fV2 := new(acl.BearerToken) - if err := fV2.Unmarshal(data); err != nil { - return err - } - - *b = *NewBearerTokenFromV2(fV2) - - return nil -} - -// MarshalJSON encodes BearerToken to protobuf JSON format. -func (b *BearerToken) MarshalJSON() ([]byte, error) { - return b.ToV2(). - MarshalJSON() -} - -// UnmarshalJSON decodes BearerToken from protobuf JSON format. -func (b *BearerToken) UnmarshalJSON(data []byte) error { - fV2 := new(acl.BearerToken) - if err := fV2.UnmarshalJSON(data); err != nil { - return err - } - - *b = *NewBearerTokenFromV2(fV2) - - return nil -} diff --git a/token/bearer_test.go b/token/bearer_test.go deleted file mode 100644 index 792a5727..00000000 --- a/token/bearer_test.go +++ /dev/null @@ -1,81 +0,0 @@ -package token_test - -import ( - "crypto/ecdsa" - "testing" - - "github.com/nspcc-dev/neo-go/pkg/crypto/keys" - "github.com/nspcc-dev/neofs-sdk-go/eacl" - "github.com/nspcc-dev/neofs-sdk-go/owner" - "github.com/nspcc-dev/neofs-sdk-go/token" - tokentest "github.com/nspcc-dev/neofs-sdk-go/token/test" - "github.com/stretchr/testify/require" -) - -func TestBearerToken_Issuer(t *testing.T) { - bearerToken := token.NewBearerToken() - - t.Run("non signed token", func(t *testing.T) { - require.Nil(t, bearerToken.Issuer()) - }) - - t.Run("signed token", func(t *testing.T) { - p, err := keys.NewPrivateKey() - require.NoError(t, err) - - ownerID := owner.NewIDFromPublicKey((*ecdsa.PublicKey)(p.PublicKey())) - - bearerToken.SetEACLTable(eacl.NewTable()) - require.NoError(t, bearerToken.SignToken(&p.PrivateKey)) - require.True(t, ownerID.Equal(bearerToken.Issuer())) - }) -} - -func TestFilterEncoding(t *testing.T) { - f := tokentest.BearerToken() - - t.Run("binary", func(t *testing.T) { - data, err := f.Marshal() - require.NoError(t, err) - - f2 := token.NewBearerToken() - require.NoError(t, f2.Unmarshal(data)) - - require.Equal(t, f, f2) - }) - - t.Run("json", func(t *testing.T) { - data, err := f.MarshalJSON() - require.NoError(t, err) - - d2 := token.NewBearerToken() - require.NoError(t, d2.UnmarshalJSON(data)) - - require.Equal(t, f, d2) - }) -} - -func TestBearerToken_ToV2(t *testing.T) { - t.Run("nil", func(t *testing.T) { - var x *token.BearerToken - - require.Nil(t, x.ToV2()) - }) -} - -func TestNewBearerToken(t *testing.T) { - t.Run("default values", func(t *testing.T) { - tkn := token.NewBearerToken() - - // convert to v2 message - tknV2 := tkn.ToV2() - - require.NotNil(t, tknV2.GetBody()) - require.Zero(t, tknV2.GetBody().GetLifetime().GetExp()) - require.Zero(t, tknV2.GetBody().GetLifetime().GetNbf()) - require.Zero(t, tknV2.GetBody().GetLifetime().GetIat()) - require.Nil(t, tknV2.GetBody().GetEACL()) - require.Nil(t, tknV2.GetBody().GetOwnerID()) - require.Nil(t, tknV2.GetSignature()) - }) -} diff --git a/token/test/generate.go b/token/test/generate.go deleted file mode 100644 index 465f20b5..00000000 --- a/token/test/generate.go +++ /dev/null @@ -1,40 +0,0 @@ -package tokentest - -import ( - "github.com/nspcc-dev/neo-go/pkg/crypto/keys" - eacltest "github.com/nspcc-dev/neofs-sdk-go/eacl/test" - ownertest "github.com/nspcc-dev/neofs-sdk-go/owner/test" - "github.com/nspcc-dev/neofs-sdk-go/token" -) - -// BearerToken returns random token.BearerToken. -// -// Resulting token is unsigned. -func BearerToken() *token.BearerToken { - x := token.NewBearerToken() - - x.SetLifetime(3, 2, 1) - x.SetOwner(ownertest.ID()) - x.SetEACLTable(eacltest.Table()) - - return x -} - -// SignedBearerToken returns signed random token.BearerToken. -// -// Panics if token could not be signed (actually unexpected). -func SignedBearerToken() *token.BearerToken { - tok := BearerToken() - - p, err := keys.NewPrivateKey() - if err != nil { - panic(err) - } - - err = tok.SignToken(&p.PrivateKey) - if err != nil { - panic(err) - } - - return tok -}