Skip to content

Commit

Permalink
eacl: Allow to set accounts as target
Browse files Browse the repository at this point in the history
Refs #483.

Signed-off-by: Evgenii Baidakov <evgenii@nspcc.io>
  • Loading branch information
smallhive committed Jul 25, 2024
1 parent 28b2676 commit 2c4e319
Show file tree
Hide file tree
Showing 6 changed files with 255 additions and 20 deletions.
60 changes: 60 additions & 0 deletions eacl/target.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import (
"crypto/ecdsa"

"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/util"
v2acl "github.com/nspcc-dev/neofs-api-go/v2/acl"
"github.com/nspcc-dev/neofs-sdk-go/user"
)

// Target is a group of request senders to match ContainerEACL. Defined by role enum
Expand Down Expand Up @@ -58,6 +60,27 @@ func (t *Target) SetBinaryKeys(keys [][]byte) {
t.keys = keys
}

// Accounts returns list of accounts to identify target subject in a binary format.
//
// Each element of the resulting slice is a [user.ID] byte account.
// Use `user := user.ID(slice)` to decode it into a type-specific structure.
//
// Instead of an account there can also be a 33 byte public key binary.
// See [keys.PublicKey].
//
// The value returned shares memory with the structure itself, so changing it can lead to data corruption.
// Make a copy if you need to change it.
func (t Target) Accounts() [][]byte {
return t.keys

Check warning on line 74 in eacl/target.go

View check run for this annotation

Codecov / codecov/patch

eacl/target.go#L73-L74

Added lines #L73 - L74 were not covered by tests
}

// SetAccounts sets list of accounts to identify target subject.
//
// Each element of the accounts parameter is a slice of bytes of [user.ID].
func (t *Target) SetAccounts(accounts [][]byte) {
t.keys = accounts
}

// SetTargetECDSAKeys converts ECDSA public keys to a binary
// format and stores them in Target.
func SetTargetECDSAKeys(t *Target, pubs ...*ecdsa.PublicKey) {
Expand All @@ -77,6 +100,43 @@ func SetTargetECDSAKeys(t *Target, pubs ...*ecdsa.PublicKey) {
t.SetBinaryKeys(binKeys)
}

// SetTargetAccounts sets accounts in Target.
func SetTargetAccounts(t *Target, accs ...util.Uint160) {
binKeys := t.BinaryKeys()
ln := len(accs)

if cap(binKeys) >= ln {
binKeys = binKeys[:0]

Check warning on line 109 in eacl/target.go

View check run for this annotation

Codecov / codecov/patch

eacl/target.go#L109

Added line #L109 was not covered by tests
} else {
binKeys = make([][]byte, 0, ln)
}

for i := 0; i < ln; i++ {
u := user.NewFromScriptHash(accs[i])
binKeys = append(binKeys, u[:])
}

t.SetAccounts(binKeys)
}

// SetTargetUsers sets user IDs in Target.
func SetTargetUsers(t *Target, accs ...user.ID) {
binKeys := t.BinaryKeys()
ln := len(accs)

if cap(binKeys) >= ln {
binKeys = binKeys[:0]

Check warning on line 128 in eacl/target.go

View check run for this annotation

Codecov / codecov/patch

eacl/target.go#L128

Added line #L128 was not covered by tests
} else {
binKeys = make([][]byte, 0, ln)
}

for i := 0; i < ln; i++ {
binKeys = append(binKeys, accs[i][:])
}

t.SetAccounts(binKeys)
}

// TargetECDSAKeys interprets binary public keys of Target
// as ECDSA public keys. If any key has a different format,
// the corresponding element will be nil.
Expand Down
55 changes: 55 additions & 0 deletions eacl/target_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import (
"testing"

"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neofs-api-go/v2/acl"
v2acl "github.com/nspcc-dev/neofs-api-go/v2/acl"
"github.com/nspcc-dev/neofs-sdk-go/user"
"github.com/stretchr/testify/require"
)

Expand Down Expand Up @@ -37,6 +39,59 @@ func TestTarget(t *testing.T) {
})
}

func TestTargetAccounts(t *testing.T) {
accs := []util.Uint160{
(*keys.PublicKey)(randomPublicKey(t)).GetScriptHash(),
(*keys.PublicKey)(randomPublicKey(t)).GetScriptHash(),
}

target := NewTarget()
target.SetRole(RoleSystem)
SetTargetAccounts(target, accs...)

v2 := target.ToV2()
require.NotNil(t, v2)
require.Equal(t, v2acl.RoleSystem, v2.GetRole())
require.Len(t, v2.GetKeys(), len(accs))
for i, key := range v2.GetKeys() {
var u = user.NewFromScriptHash(accs[i])
require.Equal(t, key, u[:])
}

newTarget := NewTargetFromV2(v2)
require.Equal(t, target, newTarget)

t.Run("from nil v2 target", func(t *testing.T) {
require.Equal(t, new(Target), NewTargetFromV2(nil))
})
}

func TestTargetUsers(t *testing.T) {
accs := []user.ID{
user.NewFromScriptHash((*keys.PublicKey)(randomPublicKey(t)).GetScriptHash()),
user.NewFromScriptHash((*keys.PublicKey)(randomPublicKey(t)).GetScriptHash()),
}

target := NewTarget()
target.SetRole(RoleSystem)
SetTargetUsers(target, accs...)

v2 := target.ToV2()
require.NotNil(t, v2)
require.Equal(t, v2acl.RoleSystem, v2.GetRole())
require.Len(t, v2.GetKeys(), len(accs))
for i, key := range v2.GetKeys() {
require.Equal(t, key, accs[i][:])
}

newTarget := NewTargetFromV2(v2)
require.Equal(t, target, newTarget)

t.Run("from nil v2 target", func(t *testing.T) {
require.Equal(t, new(Target), NewTargetFromV2(nil))
})
}

func TestTargetEncoding(t *testing.T) {
tar := NewTarget()
tar.SetRole(RoleSystem)
Expand Down
13 changes: 12 additions & 1 deletion eacl/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ type ValidationUnit struct {

key []byte

account []byte

table *Table
}

Expand Down Expand Up @@ -83,11 +85,20 @@ func (u *ValidationUnit) WithSenderKey(v []byte) *ValidationUnit {
return u
}

// WithBearerToken configures ValidationUnit to use v as request's bearer token.
// WithEACLTable configures ValidationUnit to use v as request's bearer token.
func (u *ValidationUnit) WithEACLTable(table *Table) *ValidationUnit {
if u != nil {
u.table = table
}

return u
}

// WithAccount configures ValidationUnit to use as sender's [user.ID] bytes.
func (u *ValidationUnit) WithAccount(v []byte) *ValidationUnit {
if u != nil {
u.account = v
}

return u
}
13 changes: 11 additions & 2 deletions eacl/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package eacl
import (
"bytes"
"math/big"

"github.com/nspcc-dev/neofs-sdk-go/user"
)

// Validator is a tool that calculates
Expand Down Expand Up @@ -152,8 +154,15 @@ func targetMatches(unit *ValidationUnit, record *Record) bool {
// check public key match
if pubs := target.BinaryKeys(); len(pubs) != 0 {
for _, key := range pubs {
if bytes.Equal(key, unit.key) {
return true
switch len(key) {
case user.IDSize:
if bytes.Equal(key, unit.account) {
return true
}
case 33: // pub key.
if bytes.Equal(key, unit.key) {
return true
}
}
}
continue
Expand Down
129 changes: 112 additions & 17 deletions eacl/validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"math/rand"
"testing"

usertest "github.com/nspcc-dev/neofs-sdk-go/user/test"
"github.com/stretchr/testify/require"
)

Expand Down Expand Up @@ -170,31 +171,107 @@ func TestOperationMatch(t *testing.T) {

func TestTargetMatches(t *testing.T) {
pubs := makeKeys(t, 3)
accs := accounts(3)

tgt1 := NewTarget()
tgt1.SetBinaryKeys(pubs[0:2])
tgt1.SetRole(RoleUser)
t.Run("keys", func(t *testing.T) {
tgt1 := NewTarget()
tgt1.SetBinaryKeys(pubs[0:2])
tgt1.SetRole(RoleUser)

tgt2 := NewTarget()
tgt2.SetRole(RoleOthers)
tgt2 := NewTarget()
tgt2.SetRole(RoleOthers)

r := NewRecord()
r.SetTargets(*tgt1, *tgt2)
r := NewRecord()
r.SetTargets(*tgt1, *tgt2)

u := newValidationUnit(RoleUser, pubs[0], nil)
require.True(t, targetMatches(u, r))

u = newValidationUnit(RoleUser, pubs[2], nil)
require.False(t, targetMatches(u, r))

u = newValidationUnit(RoleUnknown, pubs[1], nil)
require.True(t, targetMatches(u, r))

u = newValidationUnit(RoleOthers, pubs[2], nil)
require.True(t, targetMatches(u, r))

u = newValidationUnit(RoleSystem, pubs[2], nil)
require.False(t, targetMatches(u, r))
})

t.Run("accounts", func(t *testing.T) {
tgt1 := NewTarget()
tgt1.SetAccounts(accs[0:2])
tgt1.SetRole(RoleUser)

tgt2 := NewTarget()
tgt2.SetRole(RoleOthers)

r := NewRecord()
r.SetTargets(*tgt1, *tgt2)

u := newValidationUnitWithScriptHash(RoleUser, accs[0], nil)
require.True(t, targetMatches(u, r))

u = newValidationUnitWithScriptHash(RoleUser, accs[2], nil)
require.False(t, targetMatches(u, r))

u = newValidationUnitWithScriptHash(RoleUnknown, accs[1], nil)
require.True(t, targetMatches(u, r))

u = newValidationUnitWithScriptHash(RoleOthers, accs[2], nil)
require.True(t, targetMatches(u, r))

u = newValidationUnitWithScriptHash(RoleSystem, accs[2], nil)
require.False(t, targetMatches(u, r))
})

t.Run("mix", func(t *testing.T) {
tgt1 := NewTarget()
tgt1.SetBinaryKeys(append(pubs[0:2], accs[0:2]...))
tgt1.SetRole(RoleUser)

tgt2 := NewTarget()
tgt2.SetRole(RoleOthers)

r := NewRecord()
r.SetTargets(*tgt1, *tgt2)

u := newValidationUnit(RoleUser, pubs[0], nil)
require.True(t, targetMatches(u, r))
t.Run("user role", func(t *testing.T) {
u := newValidationUnitWithScriptHash(RoleUser, accs[0], nil)
require.True(t, targetMatches(u, r))

u = newValidationUnit(RoleUser, pubs[2], nil)
require.False(t, targetMatches(u, r))
u = newValidationUnitWithScriptHash(RoleUser, accs[2], nil)
require.False(t, targetMatches(u, r))

u = newValidationUnit(RoleUnknown, pubs[1], nil)
require.True(t, targetMatches(u, r))
u = newValidationUnit(RoleUser, pubs[0], nil)
require.True(t, targetMatches(u, r))

u = newValidationUnit(RoleOthers, pubs[2], nil)
require.True(t, targetMatches(u, r))
u = newValidationUnit(RoleUser, pubs[2], nil)
require.False(t, targetMatches(u, r))
})

u = newValidationUnit(RoleSystem, pubs[2], nil)
require.False(t, targetMatches(u, r))
t.Run("others role", func(t *testing.T) {
u := newValidationUnitWithScriptHash(RoleUnknown, accs[1], nil)
require.True(t, targetMatches(u, r))

u = newValidationUnitWithScriptHash(RoleOthers, accs[2], nil)
require.True(t, targetMatches(u, r))

u = newValidationUnitWithScriptHash(RoleSystem, accs[2], nil)
require.False(t, targetMatches(u, r))

u = newValidationUnit(RoleUnknown, pubs[1], nil)
require.True(t, targetMatches(u, r))

u = newValidationUnit(RoleOthers, pubs[2], nil)
require.True(t, targetMatches(u, r))

u = newValidationUnit(RoleSystem, pubs[2], nil)
require.False(t, targetMatches(u, r))
})
})
}

func TestSystemRoleModificationIgnored(t *testing.T) {
Expand Down Expand Up @@ -236,6 +313,17 @@ func makeKeys(t *testing.T, n int) [][]byte {
return pubs
}

func accounts(n int) [][]byte {
pubs := make([][]byte, n)
ids := usertest.IDs(n)

for i := range pubs {
pubs[i] = ids[i][:]
}

return pubs
}

type (
hdr struct {
key, value string
Expand Down Expand Up @@ -284,6 +372,13 @@ func newValidationUnit(role Role, key []byte, table *Table) *ValidationUnit {
WithEACLTable(table)
}

func newValidationUnitWithScriptHash(role Role, account []byte, table *Table) *ValidationUnit {
return new(ValidationUnit).
WithRole(role).
WithAccount(account).
WithEACLTable(table)
}

func TestNumericRules(t *testing.T) {
for _, tc := range []struct {
m Match
Expand Down
5 changes: 5 additions & 0 deletions user/id.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ func (x ID) WriteToV2(m *refs.OwnerID) {
// Deprecated: use [NewFromScriptHash] instead.
func (x *ID) SetScriptHash(scriptHash util.Uint160) { *x = NewFromScriptHash(scriptHash) }

// GetScriptHash gets scripthash from user ID.
func (x ID) GetScriptHash() util.Uint160 {
return util.Uint160(x[1:21])

Check warning on line 87 in user/id.go

View check run for this annotation

Codecov / codecov/patch

user/id.go#L86-L87

Added lines #L86 - L87 were not covered by tests
}

// WalletBytes returns NeoFS user ID as Neo3 wallet address in a binary format.
//
// The value returned shares memory with the structure itself, so changing it can lead to data corruption.
Expand Down

0 comments on commit 2c4e319

Please sign in to comment.