diff --git a/bearer/bearer.go b/bearer/bearer.go index f24bb2e..fa395a8 100644 --- a/bearer/bearer.go +++ b/bearer/bearer.go @@ -2,40 +2,121 @@ package bearer import ( "crypto/ecdsa" - "crypto/elliptic" "errors" "fmt" - "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neofs-api-go/v2/acl" "github.com/nspcc-dev/neofs-api-go/v2/refs" + cid "github.com/nspcc-dev/neofs-sdk-go/container/id" 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/eacl" "github.com/nspcc-dev/neofs-sdk-go/user" ) -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 +type Token struct { + targetUserSet bool + targetUser user.ID + + eaclTableSet bool + eaclTable eacl.Table + + lifetimeSet bool + iat, nbf, exp uint64 + + sigSet bool + sig refs.Signature +} + +// reads Token from the acl.BearerToken message. If checkFieldPresence is set, +// returns an error on absence of any protocol-required field. +func (b *Token) readFromV2(m acl.BearerToken, checkFieldPresence bool) error { + var err error + + body := m.GetBody() + if checkFieldPresence && body == nil { + return errors.New("missing token body") + } + + eaclTable := body.GetEACL() + if b.eaclTableSet = eaclTable != nil; b.eaclTableSet { + b.eaclTable = *eacl.NewTableFromV2(eaclTable) + } else if checkFieldPresence { + return errors.New("missing eACL table") + } + + targetUser := body.GetOwnerID() + if b.targetUserSet = targetUser != nil; b.targetUserSet { + err = b.targetUser.ReadFromV2(*targetUser) + if err != nil { + return fmt.Errorf("invalid target user: %w", err) + } + } + + lifetime := body.GetLifetime() + if b.lifetimeSet = lifetime != nil; b.lifetimeSet { + b.iat = lifetime.GetIat() + b.nbf = lifetime.GetNbf() + b.exp = lifetime.GetExp() + } else if checkFieldPresence { + return errors.New("missing token lifetime") + } + + sig := m.GetSignature() + if b.sigSet = sig != nil; sig != nil { + b.sig = *sig + } else if checkFieldPresence { + return errors.New("missing body signature") + } + + return nil +} // ReadFromV2 reads Token from the acl.BearerToken message. // // See also WriteToV2. -func (b *Token) ReadFromV2(m acl.BearerToken) { - *b = Token(m) +func (b *Token) ReadFromV2(m acl.BearerToken) error { + return b.readFromV2(m, true) +} + +func (b Token) fillBody() *acl.BearerTokenBody { + if !b.eaclTableSet && !b.targetUserSet && !b.lifetimeSet { + return nil + } + + var body acl.BearerTokenBody + + if b.eaclTableSet { + body.SetEACL(b.eaclTable.ToV2()) + } + + if b.targetUserSet { + var targetUser refs.OwnerID + b.targetUser.WriteToV2(&targetUser) + + body.SetOwnerID(&targetUser) + } + + if b.lifetimeSet { + var lifetime acl.TokenLifetime + lifetime.SetIat(b.iat) + lifetime.SetNbf(b.nbf) + lifetime.SetExp(b.exp) + + body.SetLifetime(&lifetime) + } + + return &body +} + +func (b Token) signedData() []byte { + return b.fillBody().StableMarshal(nil) } // WriteToV2 writes Token to the acl.BearerToken message. @@ -43,294 +124,247 @@ func (b *Token) ReadFromV2(m acl.BearerToken) { // // See also ReadFromV2. func (b Token) WriteToV2(m *acl.BearerToken) { - *m = (acl.BearerToken)(b) + m.SetBody(b.fillBody()) + + var sig *refs.Signature + + if b.sigSet { + sig = &b.sig + } + + m.SetSignature(sig) } -// SetExpiration sets "exp" (expiration time) claim which identifies the +// SetExp 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) +// See also InvalidAt. +func (b *Token) SetExp(exp uint64) { + b.exp = exp + b.lifetimeSet = true } -// 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 +// SetNbf 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) +// See also InvalidAt. +func (b *Token) SetNbf(nbf uint64) { + b.nbf = nbf + b.lifetimeSet = true } -// 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 +// SetIat 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) +// See also InvalidAt. +func (b *Token) SetIat(iat uint64) { + b.iat = iat + b.lifetimeSet = true } -// IssuedAt returns "iat" claim. +// InvalidAt asserts "exp", "nbf" and "iat" claims for the given epoch. // -// Empty Token has zero "iat". +// Zero Container is invalid in any epoch. // -// See also SetIssuedAt. -func (b Token) IssuedAt() uint64 { - v2token := (acl.BearerToken)(b) - return v2token.GetBody().GetLifetime().GetIat() +// See also SetExp, SetNbf, SetIat. +func (b Token) InvalidAt(epoch uint64) bool { + return !b.lifetimeSet || b.nbf > epoch || b.iat > epoch || b.exp <= epoch } -// SetEACLTable sets extended ACL table that should be used during object -// service request processing with bearer token. +// SetEACLTable sets eacl.Table that replaces the one from the issuer's +// container. If table has specified container, bearer token can be used only +// for operations within this specific container. Otherwise, Token can be used +// within any issuer's container. // -// See also EACLTable. +// SetEACLTable MUST be called if Token is going to be transmitted over +// NeoFS API V2 protocol. +// +// See also EACLTable, AssertContainer. 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) + b.eaclTable = table + b.eaclTableSet = true } -// EACLTable returns extended ACL table that should be used during object -// service request processing with bearer token. +// EACLTable returns extended ACL table set by SetEACLTable. +// +// Zero Token has zero eacl.Table. +func (b Token) EACLTable() eacl.Table { + if b.eaclTableSet { + return b.eaclTable + } + + return eacl.Table{} +} + +// AssertContainer checks if the token is valid within the given container. +// +// Note: cnr is assumed to refer to the issuer's container, otherwise the check +// is meaningless. +// +// Zero Token is valid in any container. // // See also SetEACLTable. -func (b Token) EACLTable() eacl.Table { - v2 := (acl.BearerToken)(b) - return *eacl.NewTableFromV2(v2.GetBody().GetEACL()) -} - -// SetOwnerID sets user.ID value of the user who can attach bearer token to -// its requests. -// -// See also OwnerID. -func (b *Token) SetOwnerID(id user.ID) { - v2 := (*acl.BearerToken)(b) - - body := v2.GetBody() - if body == nil { - body = new(acl.BearerTokenBody) +func (b Token) AssertContainer(cnr cid.ID) bool { + if !b.eaclTableSet { + return true } - var idV2 refs.OwnerID - id.WriteToV2(&idV2) - - body.SetOwnerID(&idV2) - v2.SetBody(body) + cnrTable, set := b.eaclTable.CID() + return !set || cnrTable.Equals(cnr) } -// OwnerID returns user.ID value of the user who can attach bearer token to -// its requests. +// ForUser specifies ID of the user who can use the Token for the operations +// within issuer's container(s). // -// See also SetOwnerID. -func (b Token) OwnerID() user.ID { - v2 := (acl.BearerToken)(b) - - var id user.ID - - idV2 := v2.GetBody().GetOwnerID() - if idV2 != nil { - _ = id.ReadFromV2(*idV2) - } - - return id +// Optional: by default, any user has access to Token usage. +// +// See also AssertUser. +func (b *Token) ForUser(id user.ID) { + b.targetUser = id + b.targetUserSet = true } -// 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. +// AssertUser checks if the Token is issued to the given user. // -// See also Signature. +// Zero Token is available to any user. +// +// See also ForUser. +func (b Token) AssertUser(id user.ID) bool { + return !b.targetUserSet || b.targetUser.Equals(id) +} + +// Sign calculates and writes signature of the Token data using issuer's secret. +// Returns signature calculation errors. +// +// Sign MUST be called if Token is going to be transmitted over +// NeoFS API V2 protocol. +// +// Note that any Token mutation is likely to break the signature, so it is +// expected to be calculated as a final stage of Token formation. +// +// See also VerifySignature, Issuer. func (b *Token) Sign(key ecdsa.PrivateKey) error { - err := sanityCheck(b) + var sig neofscrypto.Signature + + err := sig.Calculate(neofsecdsa.Signer(key), b.signedData()) if err != nil { return err } - m := (*acl.BearerToken)(b) - - var sig neofscrypto.Signature - - err = sig.Calculate(neofsecdsa.Signer(key), m.GetBody().StableMarshal(nil)) - if err != nil { - return fmt.Errorf("calculate signature: %w", err) - } - - var sigV2 refs.Signature - sig.WriteToV2(&sigV2) - - m.SetSignature(&sigV2) + sig.WriteToV2(&b.sig) + b.sigSet = true return nil } -// VerifySignature returns nil if bearer token contains correct signature. -func (b Token) VerifySignature() error { - if b.isEmpty() { - return nil - } - - m := (acl.BearerToken)(b) - - sigV2 := m.GetSignature() - if sigV2 == nil { - return errors.New("missing signature") - } - - var sig neofscrypto.Signature - sig.ReadFromV2(*sigV2) - - if !sig.Verify(m.GetBody().StableMarshal(nil)) { - return errors.New("wrong signature") - } - - return nil -} - -// Issuer returns user.ID associated with the key that signed bearer token. -// To pass node validation it should be owner of requested container. +// VerifySignature checks if Token signature is presented and valid. // -// If token is not signed, Issuer returns empty owner ID and false `ok` flag. +// Zero Token fails the check. // // See also Sign. -func (b Token) Issuer() (id user.ID, ok bool) { - v2 := (acl.BearerToken)(b) +func (b Token) VerifySignature() bool { + if !b.sigSet { + return false + } - pub, _ := keys.NewPublicKeyFromBytes(v2.GetSignature().GetKey(), elliptic.P256()) + var sig neofscrypto.Signature + sig.ReadFromV2(b.sig) - ok = pub != nil - if ok { - user.IDFromKey(&id, (ecdsa.PublicKey)(*pub)) + // TODO: (#233) check owner<->key relation + return sig.Verify(b.signedData()) +} + +// Marshal encodes Token into a binary format of the NeoFS API protocol +// (Protocol Buffers V3 with direct field order). +// +// See also Unmarshal. +func (b Token) Marshal() []byte { + var m acl.BearerToken + b.WriteToV2(&m) + + return m.StableMarshal(nil) +} + +// Unmarshal decodes NeoFS API protocol binary data into the Token +// (Protocol Buffers V3 with direct field order). Returns an error describing +// a format violation. +// +// See also Marshal. +func (b *Token) Unmarshal(data []byte) error { + var m acl.BearerToken + + err := m.Unmarshal(data) + if err != nil { + return err + } + + return b.readFromV2(m, false) +} + +// MarshalJSON encodes Token into a JSON format of the NeoFS API protocol +// (Protocol Buffers V3 JSON). +// +// See also UnmarshalJSON. +func (b Token) MarshalJSON() ([]byte, error) { + var m acl.BearerToken + b.WriteToV2(&m) + + return m.MarshalJSON() +} + +// UnmarshalJSON decodes NeoFS API protocol JSON data into the Token +// (Protocol Buffers V3 JSON). Returns an error describing a format violation. +// +// See also MarshalJSON. +func (b *Token) UnmarshalJSON(data []byte) error { + var m acl.BearerToken + + err := m.UnmarshalJSON(data) + if err != nil { + return err + } + + return b.readFromV2(m, false) +} + +// SigningKeyBytes returns issuer's public key in a binary format of +// NeoFS API protocol. +// +// Unsigned Token has empty key. +// +// See also ResolveIssuer. +func (b Token) SigningKeyBytes() []byte { + if b.sigSet { + return b.sig.GetKey() + } + + return nil +} + +// ResolveIssuer resolves issuer's user.ID from the key used for Token signing. +// Returns zero user.ID if Token is unsigned or key has incorrect format. +// +// See also SigningKeyBytes. +func ResolveIssuer(b Token) (usr user.ID) { + binKey := b.SigningKeyBytes() + + if len(binKey) != 0 { + var key neofsecdsa.PublicKey + if key.Decode(binKey) == nil { + user.IDFromKey(&usr, ecdsa.PublicKey(key)) + } } return } - -// 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 { - return (*acl.BearerToken)(&b).StableMarshal(nil) -} - -// 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) -} - -func (b Token) isEmpty() bool { - v2token := (acl.BearerToken)(b) - return v2token.GetBody() == nil && v2token.GetSignature() == nil -} diff --git a/bearer/bearer_test.go b/bearer/bearer_test.go index 64d1df0..e820bb7 100644 --- a/bearer/bearer_test.go +++ b/bearer/bearer_test.go @@ -1,58 +1,390 @@ package bearer_test import ( + "bytes" + "math/rand" "testing" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/nspcc-dev/neofs-api-go/v2/acl" + "github.com/nspcc-dev/neofs-api-go/v2/refs" "github.com/nspcc-dev/neofs-sdk-go/bearer" - tokentest "github.com/nspcc-dev/neofs-sdk-go/bearer/test" + bearertest "github.com/nspcc-dev/neofs-sdk-go/bearer/test" + cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test" + 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/eacl" + eacltest "github.com/nspcc-dev/neofs-sdk-go/eacl/test" "github.com/nspcc-dev/neofs-sdk-go/user" + usertest "github.com/nspcc-dev/neofs-sdk-go/user/test" "github.com/stretchr/testify/require" ) -func TestBearerToken_Issuer(t *testing.T) { - var bearerToken bearer.Token +// compares binary representations of two eacl.Table instances. +func isEqualEACLTables(t1, t2 eacl.Table) bool { + d1, err := t1.Marshal() + if err != nil { + panic(err) + } - t.Run("non signed token", func(t *testing.T) { - _, ok := bearerToken.Issuer() - require.False(t, ok) + d2, err := t2.Marshal() + if err != nil { + panic(err) + } + + return bytes.Equal(d1, d2) +} + +func TestToken_SetEACLTable(t *testing.T) { + var val bearer.Token + var m acl.BearerToken + filled := bearertest.Token() + + val.WriteToV2(&m) + require.Zero(t, m.GetBody()) + + val2 := filled + + require.NoError(t, val2.Unmarshal(val.Marshal())) + require.Zero(t, val2.EACLTable()) + + val2 = filled + + jd, err := val.MarshalJSON() + require.NoError(t, err) + + require.NoError(t, val2.UnmarshalJSON(jd)) + require.Zero(t, val2.EACLTable()) + + // set value + + eaclTable := *eacltest.Table() + + val.SetEACLTable(eaclTable) + require.True(t, isEqualEACLTables(eaclTable, val.EACLTable())) + + val.WriteToV2(&m) + eaclTableV2 := eaclTable.ToV2() + require.Equal(t, eaclTableV2, m.GetBody().GetEACL()) + + val2 = filled + + require.NoError(t, val2.Unmarshal(val.Marshal())) + require.True(t, isEqualEACLTables(eaclTable, val.EACLTable())) + + val2 = filled + + jd, err = val.MarshalJSON() + require.NoError(t, err) + + require.NoError(t, val2.UnmarshalJSON(jd)) + require.True(t, isEqualEACLTables(eaclTable, val.EACLTable())) +} + +func TestToken_ForUser(t *testing.T) { + var val bearer.Token + var m acl.BearerToken + filled := bearertest.Token() + + val.WriteToV2(&m) + require.Zero(t, m.GetBody()) + + val2 := filled + + require.NoError(t, val2.Unmarshal(val.Marshal())) + + val2.WriteToV2(&m) + require.Zero(t, m.GetBody()) + + val2 = filled + + jd, err := val.MarshalJSON() + require.NoError(t, err) + + require.NoError(t, val2.UnmarshalJSON(jd)) + + val2.WriteToV2(&m) + require.Zero(t, m.GetBody()) + + // set value + usr := *usertest.ID() + + var usrV2 refs.OwnerID + usr.WriteToV2(&usrV2) + + val.ForUser(usr) + + val.WriteToV2(&m) + require.Equal(t, usrV2, *m.GetBody().GetOwnerID()) + + val2 = filled + + require.NoError(t, val2.Unmarshal(val.Marshal())) + + val2.WriteToV2(&m) + require.Equal(t, usrV2, *m.GetBody().GetOwnerID()) + + val2 = filled + + jd, err = val.MarshalJSON() + require.NoError(t, err) + + require.NoError(t, val2.UnmarshalJSON(jd)) + + val2.WriteToV2(&m) + require.Equal(t, usrV2, *m.GetBody().GetOwnerID()) +} + +func testLifetimeClaim(t *testing.T, setter func(*bearer.Token, uint64), getter func(*acl.BearerToken) uint64) { + var val bearer.Token + var m acl.BearerToken + filled := bearertest.Token() + + val.WriteToV2(&m) + require.Zero(t, m.GetBody()) + + val2 := filled + + require.NoError(t, val2.Unmarshal(val.Marshal())) + + val2.WriteToV2(&m) + require.Zero(t, m.GetBody()) + + val2 = filled + + jd, err := val.MarshalJSON() + require.NoError(t, err) + + require.NoError(t, val2.UnmarshalJSON(jd)) + + val2.WriteToV2(&m) + require.Zero(t, m.GetBody()) + + // set value + exp := rand.Uint64() + + setter(&val, exp) + + val.WriteToV2(&m) + require.Equal(t, exp, getter(&m)) + + val2 = filled + + require.NoError(t, val2.Unmarshal(val.Marshal())) + + val2.WriteToV2(&m) + require.Equal(t, exp, getter(&m)) + + val2 = filled + + jd, err = val.MarshalJSON() + require.NoError(t, err) + + require.NoError(t, val2.UnmarshalJSON(jd)) + + val2.WriteToV2(&m) + require.Equal(t, exp, getter(&m)) +} + +func TestToken_SetLifetime(t *testing.T) { + t.Run("iat", func(t *testing.T) { + testLifetimeClaim(t, (*bearer.Token).SetIat, func(token *acl.BearerToken) uint64 { + return token.GetBody().GetLifetime().GetIat() + }) }) - t.Run("signed token", func(t *testing.T) { - p, err := keys.NewPrivateKey() - require.NoError(t, err) + t.Run("nbf", func(t *testing.T) { + testLifetimeClaim(t, (*bearer.Token).SetNbf, func(token *acl.BearerToken) uint64 { + return token.GetBody().GetLifetime().GetNbf() + }) + }) - var id user.ID - user.IDFromKey(&id, p.PrivateKey.PublicKey) - - bearerToken.SetEACLTable(*eacl.NewTable()) - require.NoError(t, bearerToken.Sign(p.PrivateKey)) - issuer, ok := bearerToken.Issuer() - require.True(t, ok) - require.True(t, id.Equals(issuer)) + t.Run("exp", func(t *testing.T) { + testLifetimeClaim(t, (*bearer.Token).SetExp, func(token *acl.BearerToken) uint64 { + return token.GetBody().GetLifetime().GetExp() + }) }) } -func TestFilterEncoding(t *testing.T) { - f := tokentest.Token() +func TestToken_InvalidAt(t *testing.T) { + var val bearer.Token - t.Run("binary", func(t *testing.T) { - data := f.Marshal() + require.True(t, val.InvalidAt(0)) + require.True(t, val.InvalidAt(1)) - var f2 bearer.Token - require.NoError(t, f2.Unmarshal(data)) + val.SetIat(1) + val.SetNbf(2) + val.SetExp(4) - 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) - }) + require.True(t, val.InvalidAt(0)) + require.True(t, val.InvalidAt(1)) + require.False(t, val.InvalidAt(2)) + require.False(t, val.InvalidAt(3)) + require.True(t, val.InvalidAt(4)) + require.True(t, val.InvalidAt(5)) +} + +func TestToken_AssertContainer(t *testing.T) { + var val bearer.Token + cnr := cidtest.ID() + + require.True(t, val.AssertContainer(cnr)) + + eaclTable := *eacltest.Table() + + eaclTable.SetCID(cidtest.ID()) + val.SetEACLTable(eaclTable) + require.False(t, val.AssertContainer(cnr)) + + eaclTable.SetCID(cnr) + val.SetEACLTable(eaclTable) + require.True(t, val.AssertContainer(cnr)) +} + +func TestToken_AssertUser(t *testing.T) { + var val bearer.Token + usr := *usertest.ID() + + require.True(t, val.AssertUser(usr)) + + val.ForUser(*usertest.ID()) + require.False(t, val.AssertUser(usr)) + + val.ForUser(usr) + require.True(t, val.AssertUser(usr)) +} + +func TestToken_Sign(t *testing.T) { + var val bearer.Token + + require.False(t, val.VerifySignature()) + + k, err := keys.NewPrivateKey() + require.NoError(t, err) + + key := k.PrivateKey + val = bearertest.Token() + + require.NoError(t, val.Sign(key)) + + require.True(t, val.VerifySignature()) + + var m acl.BearerToken + val.WriteToV2(&m) + + require.NotZero(t, m.GetSignature().GetKey()) + require.NotZero(t, m.GetSignature().GetSign()) + + val2 := bearertest.Token() + + require.NoError(t, val2.Unmarshal(val.Marshal())) + require.True(t, val2.VerifySignature()) + + jd, err := val.MarshalJSON() + require.NoError(t, err) + + val2 = bearertest.Token() + require.NoError(t, val2.UnmarshalJSON(jd)) + require.True(t, val2.VerifySignature()) +} + +func TestToken_ReadFromV2(t *testing.T) { + var val bearer.Token + var m acl.BearerToken + + require.Error(t, val.ReadFromV2(m)) + + var body acl.BearerTokenBody + m.SetBody(&body) + + require.Error(t, val.ReadFromV2(m)) + + eaclTable := eacltest.Table().ToV2() + body.SetEACL(eaclTable) + + require.Error(t, val.ReadFromV2(m)) + + var lifetime acl.TokenLifetime + body.SetLifetime(&lifetime) + + require.Error(t, val.ReadFromV2(m)) + + const iat, nbf, exp = 1, 2, 3 + lifetime.SetIat(iat) + lifetime.SetNbf(nbf) + lifetime.SetExp(exp) + + body.SetLifetime(&lifetime) + + require.Error(t, val.ReadFromV2(m)) + + var sig refs.Signature + m.SetSignature(&sig) + + require.NoError(t, val.ReadFromV2(m)) + + var m2 acl.BearerToken + + val.WriteToV2(&m2) + require.Equal(t, m, m2) + + usr, usr2 := *usertest.ID(), *usertest.ID() + + require.True(t, val.AssertUser(usr)) + require.True(t, val.AssertUser(usr2)) + + var usrV2 refs.OwnerID + usr.WriteToV2(&usrV2) + + body.SetOwnerID(&usrV2) + + require.NoError(t, val.ReadFromV2(m)) + + val.WriteToV2(&m2) + require.Equal(t, m, m2) + + require.True(t, val.AssertUser(usr)) + require.False(t, val.AssertUser(usr2)) + + k, err := keys.NewPrivateKey() + require.NoError(t, err) + + signer := neofsecdsa.Signer(k.PrivateKey) + + var s neofscrypto.Signature + + require.NoError(t, s.Calculate(signer, body.StableMarshal(nil))) + + s.WriteToV2(&sig) + + require.NoError(t, val.ReadFromV2(m)) + require.True(t, val.VerifySignature()) + require.Equal(t, sig.GetKey(), val.SigningKeyBytes()) +} + +func TestResolveIssuer(t *testing.T) { + k, err := keys.NewPrivateKey() + require.NoError(t, err) + + var val bearer.Token + + require.Zero(t, bearer.ResolveIssuer(val)) + + var m acl.BearerToken + + var sig refs.Signature + sig.SetKey([]byte("invalid key")) + + m.SetSignature(&sig) + + require.NoError(t, val.Unmarshal(m.StableMarshal(nil))) + + require.Zero(t, bearer.ResolveIssuer(val)) + + require.NoError(t, val.Sign(k.PrivateKey)) + + var usr user.ID + user.IDFromKey(&usr, k.PrivateKey.PublicKey) + + require.Equal(t, usr, bearer.ResolveIssuer(val)) } diff --git a/bearer/test/generate.go b/bearer/test/generate.go index 406eff0..65148cc 100644 --- a/bearer/test/generate.go +++ b/bearer/test/generate.go @@ -10,10 +10,10 @@ import ( // // Resulting token is unsigned. func Token() (t bearer.Token) { - t.SetExpiration(3) - t.SetNotBefore(2) - t.SetIssuedAt(1) - t.SetOwnerID(*usertest.ID()) + t.SetExp(3) + t.SetNbf(2) + t.SetIat(1) + t.ForUser(*usertest.ID()) t.SetEACLTable(*eacltest.Table()) return t