forked from TrueCloudLab/frostfs-sdk-go
[#58] eacl: move package from neofs-api-go
Also, remove deprecated methods. Signed-off-by: Evgenii Stratonikov <evgeniy@nspcc.ru>
This commit is contained in:
parent
f05ff3901f
commit
8d313dbd5d
11 changed files with 2025 additions and 0 deletions
396
eacl/enums.go
Normal file
396
eacl/enums.go
Normal file
|
@ -0,0 +1,396 @@
|
|||
package eacl
|
||||
|
||||
import (
|
||||
v2acl "github.com/nspcc-dev/neofs-api-go/v2/acl"
|
||||
)
|
||||
|
||||
// Action taken if EACL record matched request.
|
||||
// Action is compatible with v2 acl.Action enum.
|
||||
type Action uint32
|
||||
|
||||
const (
|
||||
// ActionUnknown is an Action value used to mark action as undefined.
|
||||
ActionUnknown Action = iota
|
||||
|
||||
// ActionAllow is an Action value that allows access to the operation from context.
|
||||
ActionAllow
|
||||
|
||||
// ActionDeny is an Action value that denies access to the operation from context.
|
||||
ActionDeny
|
||||
)
|
||||
|
||||
// Operation is a object service method to match request.
|
||||
// Operation is compatible with v2 acl.Operation enum.
|
||||
type Operation uint32
|
||||
|
||||
const (
|
||||
// OperationUnknown is an Operation value used to mark operation as undefined.
|
||||
OperationUnknown Operation = iota
|
||||
|
||||
// OperationGet is an object get Operation.
|
||||
OperationGet
|
||||
|
||||
// OperationHead is an Operation of getting the object header.
|
||||
OperationHead
|
||||
|
||||
// OperationPut is an object put Operation.
|
||||
OperationPut
|
||||
|
||||
// OperationDelete is an object delete Operation.
|
||||
OperationDelete
|
||||
|
||||
// OperationSearch is an object search Operation.
|
||||
OperationSearch
|
||||
|
||||
// OperationRange is an object payload range retrieval Operation.
|
||||
OperationRange
|
||||
|
||||
// OperationRangeHash is an object payload range hashing Operation.
|
||||
OperationRangeHash
|
||||
)
|
||||
|
||||
// Role is a group of request senders to match request.
|
||||
// Role is compatible with v2 acl.Role enum.
|
||||
type Role uint32
|
||||
|
||||
const (
|
||||
// RoleUnknown is a Role value used to mark role as undefined.
|
||||
RoleUnknown Role = iota
|
||||
|
||||
// RoleUser is a group of senders that contains only key of container owner.
|
||||
RoleUser
|
||||
|
||||
// RoleSystem is a group of senders that contains keys of container nodes and
|
||||
// inner ring nodes.
|
||||
RoleSystem
|
||||
|
||||
// RoleOthers is a group of senders that contains none of above keys.
|
||||
RoleOthers
|
||||
)
|
||||
|
||||
// Match is binary operation on filer name and value to check if request is matched.
|
||||
// Match is compatible with v2 acl.MatchType enum.
|
||||
type Match uint32
|
||||
|
||||
const (
|
||||
// MatchUnknown is a Match value used to mark matcher as undefined.
|
||||
MatchUnknown Match = iota
|
||||
|
||||
// MatchStringEqual is a Match of string equality.
|
||||
MatchStringEqual
|
||||
|
||||
// MatchStringNotEqual is a Match of string inequality.
|
||||
MatchStringNotEqual
|
||||
)
|
||||
|
||||
// FilterHeaderType indicates source of headers to make matches.
|
||||
// FilterHeaderType is compatible with v2 acl.HeaderType enum.
|
||||
type FilterHeaderType uint32
|
||||
|
||||
const (
|
||||
// HeaderTypeUnknown is a FilterHeaderType value used to mark header type as undefined.
|
||||
HeaderTypeUnknown FilterHeaderType = iota
|
||||
|
||||
// HeaderFromRequest is a FilterHeaderType for request X-Header.
|
||||
HeaderFromRequest
|
||||
|
||||
// HeaderFromObject is a FilterHeaderType for object header.
|
||||
HeaderFromObject
|
||||
|
||||
// HeaderFromService is a FilterHeaderType for service header.
|
||||
HeaderFromService
|
||||
)
|
||||
|
||||
// ToV2 converts Action to v2 Action enum value.
|
||||
func (a Action) ToV2() v2acl.Action {
|
||||
switch a {
|
||||
case ActionAllow:
|
||||
return v2acl.ActionAllow
|
||||
case ActionDeny:
|
||||
return v2acl.ActionDeny
|
||||
default:
|
||||
return v2acl.ActionUnknown
|
||||
}
|
||||
}
|
||||
|
||||
// ActionFromV2 converts v2 Action enum value to Action.
|
||||
func ActionFromV2(action v2acl.Action) (a Action) {
|
||||
switch action {
|
||||
case v2acl.ActionAllow:
|
||||
a = ActionAllow
|
||||
case v2acl.ActionDeny:
|
||||
a = ActionDeny
|
||||
default:
|
||||
a = ActionUnknown
|
||||
}
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
// String returns string representation of Action.
|
||||
//
|
||||
// String mapping:
|
||||
// * ActionAllow: ALLOW;
|
||||
// * ActionDeny: DENY;
|
||||
// * ActionUnknown, default: ACTION_UNSPECIFIED.
|
||||
func (a Action) String() string {
|
||||
return a.ToV2().String()
|
||||
}
|
||||
|
||||
// FromString parses Action from a string representation.
|
||||
// It is a reverse action to String().
|
||||
//
|
||||
// Returns true if s was parsed successfully.
|
||||
func (a *Action) FromString(s string) bool {
|
||||
var g v2acl.Action
|
||||
|
||||
ok := g.FromString(s)
|
||||
|
||||
if ok {
|
||||
*a = ActionFromV2(g)
|
||||
}
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
// ToV2 converts Operation to v2 Operation enum value.
|
||||
func (o Operation) ToV2() v2acl.Operation {
|
||||
switch o {
|
||||
case OperationGet:
|
||||
return v2acl.OperationGet
|
||||
case OperationHead:
|
||||
return v2acl.OperationHead
|
||||
case OperationPut:
|
||||
return v2acl.OperationPut
|
||||
case OperationDelete:
|
||||
return v2acl.OperationDelete
|
||||
case OperationSearch:
|
||||
return v2acl.OperationSearch
|
||||
case OperationRange:
|
||||
return v2acl.OperationRange
|
||||
case OperationRangeHash:
|
||||
return v2acl.OperationRangeHash
|
||||
default:
|
||||
return v2acl.OperationUnknown
|
||||
}
|
||||
}
|
||||
|
||||
// OperationFromV2 converts v2 Operation enum value to Operation.
|
||||
func OperationFromV2(operation v2acl.Operation) (o Operation) {
|
||||
switch operation {
|
||||
case v2acl.OperationGet:
|
||||
o = OperationGet
|
||||
case v2acl.OperationHead:
|
||||
o = OperationHead
|
||||
case v2acl.OperationPut:
|
||||
o = OperationPut
|
||||
case v2acl.OperationDelete:
|
||||
o = OperationDelete
|
||||
case v2acl.OperationSearch:
|
||||
o = OperationSearch
|
||||
case v2acl.OperationRange:
|
||||
o = OperationRange
|
||||
case v2acl.OperationRangeHash:
|
||||
o = OperationRangeHash
|
||||
default:
|
||||
o = OperationUnknown
|
||||
}
|
||||
|
||||
return o
|
||||
}
|
||||
|
||||
// String returns string representation of Operation.
|
||||
//
|
||||
// String mapping:
|
||||
// * OperationGet: GET;
|
||||
// * OperationHead: HEAD;
|
||||
// * OperationPut: PUT;
|
||||
// * OperationDelete: DELETE;
|
||||
// * OperationSearch: SEARCH;
|
||||
// * OperationRange: GETRANGE;
|
||||
// * OperationRangeHash: GETRANGEHASH;
|
||||
// * OperationUnknown, default: OPERATION_UNSPECIFIED.
|
||||
func (o Operation) String() string {
|
||||
return o.ToV2().String()
|
||||
}
|
||||
|
||||
// FromString parses Operation from a string representation.
|
||||
// It is a reverse action to String().
|
||||
//
|
||||
// Returns true if s was parsed successfully.
|
||||
func (o *Operation) FromString(s string) bool {
|
||||
var g v2acl.Operation
|
||||
|
||||
ok := g.FromString(s)
|
||||
|
||||
if ok {
|
||||
*o = OperationFromV2(g)
|
||||
}
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
// ToV2 converts Role to v2 Role enum value.
|
||||
func (r Role) ToV2() v2acl.Role {
|
||||
switch r {
|
||||
case RoleUser:
|
||||
return v2acl.RoleUser
|
||||
case RoleSystem:
|
||||
return v2acl.RoleSystem
|
||||
case RoleOthers:
|
||||
return v2acl.RoleOthers
|
||||
default:
|
||||
return v2acl.RoleUnknown
|
||||
}
|
||||
}
|
||||
|
||||
// RoleFromV2 converts v2 Role enum value to Role.
|
||||
func RoleFromV2(role v2acl.Role) (r Role) {
|
||||
switch role {
|
||||
case v2acl.RoleUser:
|
||||
r = RoleUser
|
||||
case v2acl.RoleSystem:
|
||||
r = RoleSystem
|
||||
case v2acl.RoleOthers:
|
||||
r = RoleOthers
|
||||
default:
|
||||
r = RoleUnknown
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// String returns string representation of Role.
|
||||
//
|
||||
// String mapping:
|
||||
// * RoleUser: USER;
|
||||
// * RoleSystem: SYSTEM;
|
||||
// * RoleOthers: OTHERS;
|
||||
// * RoleUnknown, default: ROLE_UNKNOWN.
|
||||
func (r Role) String() string {
|
||||
return r.ToV2().String()
|
||||
}
|
||||
|
||||
// FromString parses Role from a string representation.
|
||||
// It is a reverse action to String().
|
||||
//
|
||||
// Returns true if s was parsed successfully.
|
||||
func (r *Role) FromString(s string) bool {
|
||||
var g v2acl.Role
|
||||
|
||||
ok := g.FromString(s)
|
||||
|
||||
if ok {
|
||||
*r = RoleFromV2(g)
|
||||
}
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
// ToV2 converts Match to v2 MatchType enum value.
|
||||
func (m Match) ToV2() v2acl.MatchType {
|
||||
switch m {
|
||||
case MatchStringEqual:
|
||||
return v2acl.MatchTypeStringEqual
|
||||
case MatchStringNotEqual:
|
||||
return v2acl.MatchTypeStringNotEqual
|
||||
default:
|
||||
return v2acl.MatchTypeUnknown
|
||||
}
|
||||
}
|
||||
|
||||
// MatchFromV2 converts v2 MatchType enum value to Match.
|
||||
func MatchFromV2(match v2acl.MatchType) (m Match) {
|
||||
switch match {
|
||||
case v2acl.MatchTypeStringEqual:
|
||||
m = MatchStringEqual
|
||||
case v2acl.MatchTypeStringNotEqual:
|
||||
m = MatchStringNotEqual
|
||||
default:
|
||||
m = MatchUnknown
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
// String returns string representation of Match.
|
||||
//
|
||||
// String mapping:
|
||||
// * MatchStringEqual: STRING_EQUAL;
|
||||
// * MatchStringNotEqual: STRING_NOT_EQUAL;
|
||||
// * MatchUnknown, default: MATCH_TYPE_UNSPECIFIED.
|
||||
func (m Match) String() string {
|
||||
return m.ToV2().String()
|
||||
}
|
||||
|
||||
// FromString parses Match from a string representation.
|
||||
// It is a reverse action to String().
|
||||
//
|
||||
// Returns true if s was parsed successfully.
|
||||
func (m *Match) FromString(s string) bool {
|
||||
var g v2acl.MatchType
|
||||
|
||||
ok := g.FromString(s)
|
||||
|
||||
if ok {
|
||||
*m = MatchFromV2(g)
|
||||
}
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
// ToV2 converts FilterHeaderType to v2 HeaderType enum value.
|
||||
func (h FilterHeaderType) ToV2() v2acl.HeaderType {
|
||||
switch h {
|
||||
case HeaderFromRequest:
|
||||
return v2acl.HeaderTypeRequest
|
||||
case HeaderFromObject:
|
||||
return v2acl.HeaderTypeObject
|
||||
case HeaderFromService:
|
||||
return v2acl.HeaderTypeService
|
||||
default:
|
||||
return v2acl.HeaderTypeUnknown
|
||||
}
|
||||
}
|
||||
|
||||
// FilterHeaderTypeFromV2 converts v2 HeaderType enum value to FilterHeaderType.
|
||||
func FilterHeaderTypeFromV2(header v2acl.HeaderType) (h FilterHeaderType) {
|
||||
switch header {
|
||||
case v2acl.HeaderTypeRequest:
|
||||
h = HeaderFromRequest
|
||||
case v2acl.HeaderTypeObject:
|
||||
h = HeaderFromObject
|
||||
case v2acl.HeaderTypeService:
|
||||
h = HeaderFromService
|
||||
default:
|
||||
h = HeaderTypeUnknown
|
||||
}
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
// String returns string representation of FilterHeaderType.
|
||||
//
|
||||
// String mapping:
|
||||
// * HeaderFromRequest: REQUEST;
|
||||
// * HeaderFromObject: OBJECT;
|
||||
// * HeaderTypeUnknown, default: HEADER_UNSPECIFIED.
|
||||
func (h FilterHeaderType) String() string {
|
||||
return h.ToV2().String()
|
||||
}
|
||||
|
||||
// FromString parses FilterHeaderType from a string representation.
|
||||
// It is a reverse action to String().
|
||||
//
|
||||
// Returns true if s was parsed successfully.
|
||||
func (h *FilterHeaderType) FromString(s string) bool {
|
||||
var g v2acl.HeaderType
|
||||
|
||||
ok := g.FromString(s)
|
||||
|
||||
if ok {
|
||||
*h = FilterHeaderTypeFromV2(g)
|
||||
}
|
||||
|
||||
return ok
|
||||
}
|
214
eacl/enums_test.go
Normal file
214
eacl/enums_test.go
Normal file
|
@ -0,0 +1,214 @@
|
|||
package eacl_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
v2acl "github.com/nspcc-dev/neofs-api-go/v2/acl"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/eacl"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var (
|
||||
eqV2Actions = map[eacl.Action]v2acl.Action{
|
||||
eacl.ActionUnknown: v2acl.ActionUnknown,
|
||||
eacl.ActionAllow: v2acl.ActionAllow,
|
||||
eacl.ActionDeny: v2acl.ActionDeny,
|
||||
}
|
||||
|
||||
eqV2Operations = map[eacl.Operation]v2acl.Operation{
|
||||
eacl.OperationUnknown: v2acl.OperationUnknown,
|
||||
eacl.OperationGet: v2acl.OperationGet,
|
||||
eacl.OperationHead: v2acl.OperationHead,
|
||||
eacl.OperationPut: v2acl.OperationPut,
|
||||
eacl.OperationDelete: v2acl.OperationDelete,
|
||||
eacl.OperationSearch: v2acl.OperationSearch,
|
||||
eacl.OperationRange: v2acl.OperationRange,
|
||||
eacl.OperationRangeHash: v2acl.OperationRangeHash,
|
||||
}
|
||||
|
||||
eqV2Roles = map[eacl.Role]v2acl.Role{
|
||||
eacl.RoleUnknown: v2acl.RoleUnknown,
|
||||
eacl.RoleUser: v2acl.RoleUser,
|
||||
eacl.RoleSystem: v2acl.RoleSystem,
|
||||
eacl.RoleOthers: v2acl.RoleOthers,
|
||||
}
|
||||
|
||||
eqV2Matches = map[eacl.Match]v2acl.MatchType{
|
||||
eacl.MatchUnknown: v2acl.MatchTypeUnknown,
|
||||
eacl.MatchStringEqual: v2acl.MatchTypeStringEqual,
|
||||
eacl.MatchStringNotEqual: v2acl.MatchTypeStringNotEqual,
|
||||
}
|
||||
|
||||
eqV2HeaderTypes = map[eacl.FilterHeaderType]v2acl.HeaderType{
|
||||
eacl.HeaderTypeUnknown: v2acl.HeaderTypeUnknown,
|
||||
eacl.HeaderFromRequest: v2acl.HeaderTypeRequest,
|
||||
eacl.HeaderFromObject: v2acl.HeaderTypeObject,
|
||||
eacl.HeaderFromService: v2acl.HeaderTypeService,
|
||||
}
|
||||
)
|
||||
|
||||
func TestAction(t *testing.T) {
|
||||
t.Run("known actions", func(t *testing.T) {
|
||||
for i := eacl.ActionUnknown; i <= eacl.ActionDeny; i++ {
|
||||
require.Equal(t, eqV2Actions[i], i.ToV2())
|
||||
require.Equal(t, eacl.ActionFromV2(i.ToV2()), i)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("unknown actions", func(t *testing.T) {
|
||||
require.Equal(t, (eacl.ActionDeny + 1).ToV2(), v2acl.ActionUnknown)
|
||||
require.Equal(t, eacl.ActionFromV2(v2acl.ActionDeny+1), eacl.ActionUnknown)
|
||||
})
|
||||
}
|
||||
|
||||
func TestOperation(t *testing.T) {
|
||||
t.Run("known operations", func(t *testing.T) {
|
||||
for i := eacl.OperationUnknown; i <= eacl.OperationRangeHash; i++ {
|
||||
require.Equal(t, eqV2Operations[i], i.ToV2())
|
||||
require.Equal(t, eacl.OperationFromV2(i.ToV2()), i)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("unknown operations", func(t *testing.T) {
|
||||
require.Equal(t, (eacl.OperationRangeHash + 1).ToV2(), v2acl.OperationUnknown)
|
||||
require.Equal(t, eacl.OperationFromV2(v2acl.OperationRangeHash+1), eacl.OperationUnknown)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRole(t *testing.T) {
|
||||
t.Run("known roles", func(t *testing.T) {
|
||||
for i := eacl.RoleUnknown; i <= eacl.RoleOthers; i++ {
|
||||
require.Equal(t, eqV2Roles[i], i.ToV2())
|
||||
require.Equal(t, eacl.RoleFromV2(i.ToV2()), i)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("unknown roles", func(t *testing.T) {
|
||||
require.Equal(t, (eacl.RoleOthers + 1).ToV2(), v2acl.RoleUnknown)
|
||||
require.Equal(t, eacl.RoleFromV2(v2acl.RoleOthers+1), eacl.RoleUnknown)
|
||||
})
|
||||
}
|
||||
|
||||
func TestMatch(t *testing.T) {
|
||||
t.Run("known matches", func(t *testing.T) {
|
||||
for i := eacl.MatchUnknown; i <= eacl.MatchStringNotEqual; i++ {
|
||||
require.Equal(t, eqV2Matches[i], i.ToV2())
|
||||
require.Equal(t, eacl.MatchFromV2(i.ToV2()), i)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("unknown matches", func(t *testing.T) {
|
||||
require.Equal(t, (eacl.MatchStringNotEqual + 1).ToV2(), v2acl.MatchTypeUnknown)
|
||||
require.Equal(t, eacl.MatchFromV2(v2acl.MatchTypeStringNotEqual+1), eacl.MatchUnknown)
|
||||
})
|
||||
}
|
||||
|
||||
func TestFilterHeaderType(t *testing.T) {
|
||||
t.Run("known header types", func(t *testing.T) {
|
||||
for i := eacl.HeaderTypeUnknown; i <= eacl.HeaderFromService; i++ {
|
||||
require.Equal(t, eqV2HeaderTypes[i], i.ToV2())
|
||||
require.Equal(t, eacl.FilterHeaderTypeFromV2(i.ToV2()), i)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("unknown header types", func(t *testing.T) {
|
||||
require.Equal(t, (eacl.HeaderFromService + 1).ToV2(), v2acl.HeaderTypeUnknown)
|
||||
require.Equal(t, eacl.FilterHeaderTypeFromV2(v2acl.HeaderTypeService+1), eacl.HeaderTypeUnknown)
|
||||
})
|
||||
}
|
||||
|
||||
type enumIface interface {
|
||||
FromString(string) bool
|
||||
String() string
|
||||
}
|
||||
|
||||
type enumStringItem struct {
|
||||
val enumIface
|
||||
str string
|
||||
}
|
||||
|
||||
func testEnumStrings(t *testing.T, e enumIface, items []enumStringItem) {
|
||||
for _, item := range items {
|
||||
require.Equal(t, item.str, item.val.String())
|
||||
|
||||
s := item.val.String()
|
||||
|
||||
require.True(t, e.FromString(s), s)
|
||||
|
||||
require.EqualValues(t, item.val, e, item.val)
|
||||
}
|
||||
|
||||
// incorrect strings
|
||||
for _, str := range []string{
|
||||
"some string",
|
||||
"UNSPECIFIED",
|
||||
} {
|
||||
require.False(t, e.FromString(str))
|
||||
}
|
||||
}
|
||||
|
||||
func TestAction_String(t *testing.T) {
|
||||
toPtr := func(v eacl.Action) *eacl.Action {
|
||||
return &v
|
||||
}
|
||||
|
||||
testEnumStrings(t, new(eacl.Action), []enumStringItem{
|
||||
{val: toPtr(eacl.ActionAllow), str: "ALLOW"},
|
||||
{val: toPtr(eacl.ActionDeny), str: "DENY"},
|
||||
{val: toPtr(eacl.ActionUnknown), str: "ACTION_UNSPECIFIED"},
|
||||
})
|
||||
}
|
||||
|
||||
func TestRole_String(t *testing.T) {
|
||||
toPtr := func(v eacl.Role) *eacl.Role {
|
||||
return &v
|
||||
}
|
||||
|
||||
testEnumStrings(t, new(eacl.Role), []enumStringItem{
|
||||
{val: toPtr(eacl.RoleUser), str: "USER"},
|
||||
{val: toPtr(eacl.RoleSystem), str: "SYSTEM"},
|
||||
{val: toPtr(eacl.RoleOthers), str: "OTHERS"},
|
||||
{val: toPtr(eacl.RoleUnknown), str: "ROLE_UNSPECIFIED"},
|
||||
})
|
||||
}
|
||||
|
||||
func TestOperation_String(t *testing.T) {
|
||||
toPtr := func(v eacl.Operation) *eacl.Operation {
|
||||
return &v
|
||||
}
|
||||
|
||||
testEnumStrings(t, new(eacl.Operation), []enumStringItem{
|
||||
{val: toPtr(eacl.OperationGet), str: "GET"},
|
||||
{val: toPtr(eacl.OperationPut), str: "PUT"},
|
||||
{val: toPtr(eacl.OperationHead), str: "HEAD"},
|
||||
{val: toPtr(eacl.OperationDelete), str: "DELETE"},
|
||||
{val: toPtr(eacl.OperationSearch), str: "SEARCH"},
|
||||
{val: toPtr(eacl.OperationRange), str: "GETRANGE"},
|
||||
{val: toPtr(eacl.OperationRangeHash), str: "GETRANGEHASH"},
|
||||
{val: toPtr(eacl.OperationUnknown), str: "OPERATION_UNSPECIFIED"},
|
||||
})
|
||||
}
|
||||
|
||||
func TestMatch_String(t *testing.T) {
|
||||
toPtr := func(v eacl.Match) *eacl.Match {
|
||||
return &v
|
||||
}
|
||||
|
||||
testEnumStrings(t, new(eacl.Match), []enumStringItem{
|
||||
{val: toPtr(eacl.MatchStringEqual), str: "STRING_EQUAL"},
|
||||
{val: toPtr(eacl.MatchStringNotEqual), str: "STRING_NOT_EQUAL"},
|
||||
{val: toPtr(eacl.MatchUnknown), str: "MATCH_TYPE_UNSPECIFIED"},
|
||||
})
|
||||
}
|
||||
|
||||
func TestFilterHeaderType_String(t *testing.T) {
|
||||
toPtr := func(v eacl.FilterHeaderType) *eacl.FilterHeaderType {
|
||||
return &v
|
||||
}
|
||||
|
||||
testEnumStrings(t, new(eacl.FilterHeaderType), []enumStringItem{
|
||||
{val: toPtr(eacl.HeaderFromRequest), str: "REQUEST"},
|
||||
{val: toPtr(eacl.HeaderFromObject), str: "OBJECT"},
|
||||
{val: toPtr(eacl.HeaderTypeUnknown), str: "HEADER_UNSPECIFIED"},
|
||||
})
|
||||
}
|
176
eacl/filter.go
Normal file
176
eacl/filter.go
Normal file
|
@ -0,0 +1,176 @@
|
|||
package eacl
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
v2acl "github.com/nspcc-dev/neofs-api-go/v2/acl"
|
||||
)
|
||||
|
||||
// Filter defines check conditions if request header is matched or not. Matched
|
||||
// header means that request should be processed according to EACL action.
|
||||
//
|
||||
// Filter is compatible with v2 acl.EACLRecord.Filter message.
|
||||
type Filter struct {
|
||||
from FilterHeaderType
|
||||
matcher Match
|
||||
key filterKey
|
||||
value fmt.Stringer
|
||||
}
|
||||
|
||||
type staticStringer string
|
||||
|
||||
type u64Stringer uint64
|
||||
|
||||
type filterKey struct {
|
||||
typ filterKeyType
|
||||
|
||||
str string
|
||||
}
|
||||
|
||||
// enumeration of reserved filter keys.
|
||||
type filterKeyType int
|
||||
|
||||
const (
|
||||
_ filterKeyType = iota
|
||||
fKeyObjVersion
|
||||
fKeyObjID
|
||||
fKeyObjContainerID
|
||||
fKeyObjOwnerID
|
||||
fKeyObjCreationEpoch
|
||||
fKeyObjPayloadLength
|
||||
fKeyObjPayloadHash
|
||||
fKeyObjType
|
||||
fKeyObjHomomorphicHash
|
||||
)
|
||||
|
||||
func (s staticStringer) String() string {
|
||||
return string(s)
|
||||
}
|
||||
|
||||
func (u u64Stringer) String() string {
|
||||
return strconv.FormatUint(uint64(u), 10)
|
||||
}
|
||||
|
||||
// Value returns filtered string value.
|
||||
func (f Filter) Value() string {
|
||||
return f.value.String()
|
||||
}
|
||||
|
||||
// Matcher returns filter Match type.
|
||||
func (f Filter) Matcher() Match {
|
||||
return f.matcher
|
||||
}
|
||||
|
||||
// Key returns key to the filtered header.
|
||||
func (f Filter) Key() string {
|
||||
return f.key.String()
|
||||
}
|
||||
|
||||
// From returns FilterHeaderType that defined which header will be filtered.
|
||||
func (f Filter) From() FilterHeaderType {
|
||||
return f.from
|
||||
}
|
||||
|
||||
// ToV2 converts Filter to v2 acl.EACLRecord.Filter message.
|
||||
//
|
||||
// Nil Filter converts to nil.
|
||||
func (f *Filter) ToV2() *v2acl.HeaderFilter {
|
||||
if f == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
filter := new(v2acl.HeaderFilter)
|
||||
filter.SetValue(f.value.String())
|
||||
filter.SetKey(f.key.String())
|
||||
filter.SetMatchType(f.matcher.ToV2())
|
||||
filter.SetHeaderType(f.from.ToV2())
|
||||
|
||||
return filter
|
||||
}
|
||||
|
||||
func (k filterKey) String() string {
|
||||
switch k.typ {
|
||||
default:
|
||||
return k.str
|
||||
case fKeyObjVersion:
|
||||
return v2acl.FilterObjectVersion
|
||||
case fKeyObjID:
|
||||
return v2acl.FilterObjectID
|
||||
case fKeyObjContainerID:
|
||||
return v2acl.FilterObjectContainerID
|
||||
case fKeyObjOwnerID:
|
||||
return v2acl.FilterObjectOwnerID
|
||||
case fKeyObjCreationEpoch:
|
||||
return v2acl.FilterObjectCreationEpoch
|
||||
case fKeyObjPayloadLength:
|
||||
return v2acl.FilterObjectPayloadLength
|
||||
case fKeyObjPayloadHash:
|
||||
return v2acl.FilterObjectPayloadHash
|
||||
case fKeyObjType:
|
||||
return v2acl.FilterObjectType
|
||||
case fKeyObjHomomorphicHash:
|
||||
return v2acl.FilterObjectHomomorphicHash
|
||||
}
|
||||
}
|
||||
|
||||
// NewFilter creates, initializes and returns blank Filter instance.
|
||||
//
|
||||
// Defaults:
|
||||
// - header type: HeaderTypeUnknown;
|
||||
// - matcher: MatchUnknown;
|
||||
// - key: "";
|
||||
// - value: "".
|
||||
func NewFilter() *Filter {
|
||||
return NewFilterFromV2(new(v2acl.HeaderFilter))
|
||||
}
|
||||
|
||||
// NewFilterFromV2 converts v2 acl.EACLRecord.Filter message to Filter.
|
||||
func NewFilterFromV2(filter *v2acl.HeaderFilter) *Filter {
|
||||
f := new(Filter)
|
||||
|
||||
if filter == nil {
|
||||
return f
|
||||
}
|
||||
|
||||
f.from = FilterHeaderTypeFromV2(filter.GetHeaderType())
|
||||
f.matcher = MatchFromV2(filter.GetMatchType())
|
||||
f.key.str = filter.GetKey()
|
||||
f.value = staticStringer(filter.GetValue())
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
// Marshal marshals Filter into a protobuf binary form.
|
||||
func (f *Filter) Marshal() ([]byte, error) {
|
||||
return f.ToV2().StableMarshal(nil)
|
||||
}
|
||||
|
||||
// Unmarshal unmarshals protobuf binary representation of Filter.
|
||||
func (f *Filter) Unmarshal(data []byte) error {
|
||||
fV2 := new(v2acl.HeaderFilter)
|
||||
if err := fV2.Unmarshal(data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*f = *NewFilterFromV2(fV2)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON encodes Filter to protobuf JSON format.
|
||||
func (f *Filter) MarshalJSON() ([]byte, error) {
|
||||
return f.ToV2().MarshalJSON()
|
||||
}
|
||||
|
||||
// UnmarshalJSON decodes Filter from protobuf JSON format.
|
||||
func (f *Filter) UnmarshalJSON(data []byte) error {
|
||||
fV2 := new(v2acl.HeaderFilter)
|
||||
if err := fV2.UnmarshalJSON(data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*f = *NewFilterFromV2(fV2)
|
||||
|
||||
return nil
|
||||
}
|
88
eacl/filter_test.go
Normal file
88
eacl/filter_test.go
Normal file
|
@ -0,0 +1,88 @@
|
|||
package eacl
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/acl"
|
||||
v2acl "github.com/nspcc-dev/neofs-api-go/v2/acl"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func newObjectFilter(match Match, key, val string) *Filter {
|
||||
return &Filter{
|
||||
from: HeaderFromObject,
|
||||
key: filterKey{
|
||||
str: key,
|
||||
},
|
||||
matcher: match,
|
||||
value: staticStringer(val),
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilter(t *testing.T) {
|
||||
filter := newObjectFilter(MatchStringEqual, "some name", "200")
|
||||
|
||||
v2 := filter.ToV2()
|
||||
require.NotNil(t, v2)
|
||||
require.Equal(t, v2acl.HeaderTypeObject, v2.GetHeaderType())
|
||||
require.EqualValues(t, v2acl.MatchTypeStringEqual, v2.GetMatchType())
|
||||
require.Equal(t, filter.Key(), v2.GetKey())
|
||||
require.Equal(t, filter.Value(), v2.GetValue())
|
||||
|
||||
newFilter := NewFilterFromV2(v2)
|
||||
require.Equal(t, filter, newFilter)
|
||||
|
||||
t.Run("from nil v2 filter", func(t *testing.T) {
|
||||
require.Equal(t, new(Filter), NewFilterFromV2(nil))
|
||||
})
|
||||
}
|
||||
|
||||
func TestFilterEncoding(t *testing.T) {
|
||||
f := newObjectFilter(MatchStringEqual, "key", "value")
|
||||
|
||||
t.Run("binary", func(t *testing.T) {
|
||||
data, err := f.Marshal()
|
||||
require.NoError(t, err)
|
||||
|
||||
f2 := NewFilter()
|
||||
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 := NewFilter()
|
||||
require.NoError(t, d2.UnmarshalJSON(data))
|
||||
|
||||
require.Equal(t, f, d2)
|
||||
})
|
||||
}
|
||||
|
||||
func TestFilter_ToV2(t *testing.T) {
|
||||
t.Run("nil", func(t *testing.T) {
|
||||
var x *Filter
|
||||
|
||||
require.Nil(t, x.ToV2())
|
||||
})
|
||||
|
||||
t.Run("default values", func(t *testing.T) {
|
||||
filter := NewFilter()
|
||||
|
||||
// check initial values
|
||||
require.Empty(t, filter.Key())
|
||||
require.Empty(t, filter.Value())
|
||||
require.Equal(t, HeaderTypeUnknown, filter.From())
|
||||
require.Equal(t, MatchUnknown, filter.Matcher())
|
||||
|
||||
// convert to v2 message
|
||||
filterV2 := filter.ToV2()
|
||||
|
||||
require.Empty(t, filterV2.GetKey())
|
||||
require.Empty(t, filterV2.GetValue())
|
||||
require.Equal(t, acl.HeaderTypeUnknown, filterV2.GetHeaderType())
|
||||
require.Equal(t, acl.MatchTypeUnknown, filterV2.GetMatchType())
|
||||
})
|
||||
}
|
268
eacl/record.go
Normal file
268
eacl/record.go
Normal file
|
@ -0,0 +1,268 @@
|
|||
package eacl
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"fmt"
|
||||
|
||||
v2acl "github.com/nspcc-dev/neofs-api-go/v2/acl"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/checksum"
|
||||
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/object"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/owner"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/version"
|
||||
)
|
||||
|
||||
// Record of the EACL rule, that defines EACL action, targets for this action,
|
||||
// object service operation and filters for request headers.
|
||||
//
|
||||
// Record is compatible with v2 acl.EACLRecord message.
|
||||
type Record struct {
|
||||
action Action
|
||||
operation Operation
|
||||
filters []*Filter
|
||||
targets []*Target
|
||||
}
|
||||
|
||||
// Targets returns list of target subjects to apply ACL rule to.
|
||||
func (r Record) Targets() []*Target {
|
||||
return r.targets
|
||||
}
|
||||
|
||||
// SetTargets sets list of target subjects to apply ACL rule to.
|
||||
func (r *Record) SetTargets(targets ...*Target) {
|
||||
r.targets = targets
|
||||
}
|
||||
|
||||
// Filters returns list of filters to match and see if rule is applicable.
|
||||
func (r Record) Filters() []*Filter {
|
||||
return r.filters
|
||||
}
|
||||
|
||||
// Operation returns NeoFS request verb to match.
|
||||
func (r Record) Operation() Operation {
|
||||
return r.operation
|
||||
}
|
||||
|
||||
// SetOperation sets NeoFS request verb to match.
|
||||
func (r *Record) SetOperation(operation Operation) {
|
||||
r.operation = operation
|
||||
}
|
||||
|
||||
// Action returns rule execution result.
|
||||
func (r Record) Action() Action {
|
||||
return r.action
|
||||
}
|
||||
|
||||
// SetAction sets rule execution result.
|
||||
func (r *Record) SetAction(action Action) {
|
||||
r.action = action
|
||||
}
|
||||
|
||||
// AddRecordTarget adds single Target to the Record.
|
||||
func AddRecordTarget(r *Record, t *Target) {
|
||||
r.SetTargets(append(r.Targets(), t)...)
|
||||
}
|
||||
|
||||
// AddFormedTarget forms Target with specified Role and list of
|
||||
// ECDSA public keys and adds it to the Record.
|
||||
func AddFormedTarget(r *Record, role Role, keys ...ecdsa.PublicKey) {
|
||||
t := NewTarget()
|
||||
t.SetRole(role)
|
||||
|
||||
SetTargetECDSAKeys(t, ecdsaKeysToPtrs(keys)...)
|
||||
AddRecordTarget(r, t)
|
||||
}
|
||||
|
||||
func (r *Record) addFilter(from FilterHeaderType, m Match, keyTyp filterKeyType, key string, val fmt.Stringer) {
|
||||
filter := &Filter{
|
||||
from: from,
|
||||
key: filterKey{
|
||||
typ: keyTyp,
|
||||
str: key,
|
||||
},
|
||||
matcher: m,
|
||||
value: val,
|
||||
}
|
||||
|
||||
r.filters = append(r.filters, filter)
|
||||
}
|
||||
|
||||
func (r *Record) addObjectFilter(m Match, keyTyp filterKeyType, key string, val fmt.Stringer) {
|
||||
r.addFilter(HeaderFromObject, m, keyTyp, key, val)
|
||||
}
|
||||
|
||||
func (r *Record) addObjectReservedFilter(m Match, typ filterKeyType, val fmt.Stringer) {
|
||||
r.addObjectFilter(m, typ, "", val)
|
||||
}
|
||||
|
||||
// AddFilter adds generic filter.
|
||||
func (r *Record) AddFilter(from FilterHeaderType, matcher Match, name, value string) {
|
||||
r.addFilter(from, matcher, 0, name, staticStringer(value))
|
||||
}
|
||||
|
||||
// AddObjectAttributeFilter adds filter by object attribute.
|
||||
func (r *Record) AddObjectAttributeFilter(m Match, key, value string) {
|
||||
r.addObjectFilter(m, 0, key, staticStringer(value))
|
||||
}
|
||||
|
||||
// AddObjectVersionFilter adds filter by object version.
|
||||
func (r *Record) AddObjectVersionFilter(m Match, v *version.Version) {
|
||||
r.addObjectReservedFilter(m, fKeyObjVersion, v)
|
||||
}
|
||||
|
||||
// AddObjectIDFilter adds filter by object ID.
|
||||
func (r *Record) AddObjectIDFilter(m Match, id *object.ID) {
|
||||
r.addObjectReservedFilter(m, fKeyObjID, id)
|
||||
}
|
||||
|
||||
// AddObjectContainerIDFilter adds filter by object container ID.
|
||||
func (r *Record) AddObjectContainerIDFilter(m Match, id *cid.ID) {
|
||||
r.addObjectReservedFilter(m, fKeyObjContainerID, id)
|
||||
}
|
||||
|
||||
// AddObjectOwnerIDFilter adds filter by object owner ID.
|
||||
func (r *Record) AddObjectOwnerIDFilter(m Match, id *owner.ID) {
|
||||
r.addObjectReservedFilter(m, fKeyObjOwnerID, id)
|
||||
}
|
||||
|
||||
// AddObjectCreationEpoch adds filter by object creation epoch.
|
||||
func (r *Record) AddObjectCreationEpoch(m Match, epoch uint64) {
|
||||
r.addObjectReservedFilter(m, fKeyObjCreationEpoch, u64Stringer(epoch))
|
||||
}
|
||||
|
||||
// AddObjectPayloadLengthFilter adds filter by object payload length.
|
||||
func (r *Record) AddObjectPayloadLengthFilter(m Match, size uint64) {
|
||||
r.addObjectReservedFilter(m, fKeyObjPayloadLength, u64Stringer(size))
|
||||
}
|
||||
|
||||
// AddObjectPayloadHashFilter adds filter by object payload hash value.
|
||||
func (r *Record) AddObjectPayloadHashFilter(m Match, h *checksum.Checksum) {
|
||||
r.addObjectReservedFilter(m, fKeyObjPayloadHash, h)
|
||||
}
|
||||
|
||||
// AddObjectTypeFilter adds filter by object type.
|
||||
func (r *Record) AddObjectTypeFilter(m Match, t object.Type) {
|
||||
r.addObjectReservedFilter(m, fKeyObjType, t)
|
||||
}
|
||||
|
||||
// AddObjectHomomorphicHashFilter adds filter by object payload homomorphic hash value.
|
||||
func (r *Record) AddObjectHomomorphicHashFilter(m Match, h *checksum.Checksum) {
|
||||
r.addObjectReservedFilter(m, fKeyObjHomomorphicHash, h)
|
||||
}
|
||||
|
||||
// ToV2 converts Record to v2 acl.EACLRecord message.
|
||||
//
|
||||
// Nil Record converts to nil.
|
||||
func (r *Record) ToV2() *v2acl.Record {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
v2 := new(v2acl.Record)
|
||||
|
||||
if r.targets != nil {
|
||||
targets := make([]*v2acl.Target, 0, len(r.targets))
|
||||
for _, target := range r.targets {
|
||||
targets = append(targets, target.ToV2())
|
||||
}
|
||||
|
||||
v2.SetTargets(targets)
|
||||
}
|
||||
|
||||
if r.filters != nil {
|
||||
filters := make([]*v2acl.HeaderFilter, 0, len(r.filters))
|
||||
for _, filter := range r.filters {
|
||||
filters = append(filters, filter.ToV2())
|
||||
}
|
||||
|
||||
v2.SetFilters(filters)
|
||||
}
|
||||
|
||||
v2.SetAction(r.action.ToV2())
|
||||
v2.SetOperation(r.operation.ToV2())
|
||||
|
||||
return v2
|
||||
}
|
||||
|
||||
// NewRecord creates and returns blank Record instance.
|
||||
//
|
||||
// Defaults:
|
||||
// - action: ActionUnknown;
|
||||
// - operation: OperationUnknown;
|
||||
// - targets: nil,
|
||||
// - filters: nil.
|
||||
func NewRecord() *Record {
|
||||
return new(Record)
|
||||
}
|
||||
|
||||
// CreateRecord creates, initializes with parameters and returns Record instance.
|
||||
func CreateRecord(action Action, operation Operation) *Record {
|
||||
r := NewRecord()
|
||||
r.action = action
|
||||
r.operation = operation
|
||||
r.targets = []*Target{}
|
||||
r.filters = []*Filter{}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// NewRecordFromV2 converts v2 acl.EACLRecord message to Record.
|
||||
func NewRecordFromV2(record *v2acl.Record) *Record {
|
||||
r := NewRecord()
|
||||
|
||||
if record == nil {
|
||||
return r
|
||||
}
|
||||
|
||||
r.action = ActionFromV2(record.GetAction())
|
||||
r.operation = OperationFromV2(record.GetOperation())
|
||||
|
||||
v2targets := record.GetTargets()
|
||||
v2filters := record.GetFilters()
|
||||
|
||||
r.targets = make([]*Target, 0, len(v2targets))
|
||||
for i := range v2targets {
|
||||
r.targets = append(r.targets, NewTargetFromV2(v2targets[i]))
|
||||
}
|
||||
|
||||
r.filters = make([]*Filter, 0, len(v2filters))
|
||||
for i := range v2filters {
|
||||
r.filters = append(r.filters, NewFilterFromV2(v2filters[i]))
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// Marshal marshals Record into a protobuf binary form.
|
||||
func (r *Record) Marshal() ([]byte, error) {
|
||||
return r.ToV2().StableMarshal(nil)
|
||||
}
|
||||
|
||||
// Unmarshal unmarshals protobuf binary representation of Record.
|
||||
func (r *Record) Unmarshal(data []byte) error {
|
||||
fV2 := new(v2acl.Record)
|
||||
if err := fV2.Unmarshal(data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*r = *NewRecordFromV2(fV2)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON encodes Record to protobuf JSON format.
|
||||
func (r *Record) MarshalJSON() ([]byte, error) {
|
||||
return r.ToV2().MarshalJSON()
|
||||
}
|
||||
|
||||
// UnmarshalJSON decodes Record from protobuf JSON format.
|
||||
func (r *Record) UnmarshalJSON(data []byte) error {
|
||||
tV2 := new(v2acl.Record)
|
||||
if err := tV2.UnmarshalJSON(data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*r = *NewRecordFromV2(tV2)
|
||||
|
||||
return nil
|
||||
}
|
258
eacl/record_test.go
Normal file
258
eacl/record_test.go
Normal file
|
@ -0,0 +1,258 @@
|
|||
package eacl
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
v2acl "github.com/nspcc-dev/neofs-api-go/v2/acl"
|
||||
checksumtest "github.com/nspcc-dev/neofs-sdk-go/checksum/test"
|
||||
cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/object"
|
||||
objecttest "github.com/nspcc-dev/neofs-sdk-go/object/test"
|
||||
ownertest "github.com/nspcc-dev/neofs-sdk-go/owner/test"
|
||||
versiontest "github.com/nspcc-dev/neofs-sdk-go/version/test"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestRecord(t *testing.T) {
|
||||
record := NewRecord()
|
||||
record.SetOperation(OperationRange)
|
||||
record.SetAction(ActionAllow)
|
||||
record.AddFilter(HeaderFromRequest, MatchStringEqual, "A", "B")
|
||||
record.AddFilter(HeaderFromRequest, MatchStringNotEqual, "C", "D")
|
||||
|
||||
target := NewTarget()
|
||||
target.SetRole(RoleSystem)
|
||||
AddRecordTarget(record, target)
|
||||
|
||||
v2 := record.ToV2()
|
||||
require.NotNil(t, v2)
|
||||
require.Equal(t, v2acl.OperationRange, v2.GetOperation())
|
||||
require.Equal(t, v2acl.ActionAllow, v2.GetAction())
|
||||
require.Len(t, v2.GetFilters(), len(record.Filters()))
|
||||
require.Len(t, v2.GetTargets(), len(record.Targets()))
|
||||
|
||||
newRecord := NewRecordFromV2(v2)
|
||||
require.Equal(t, record, newRecord)
|
||||
|
||||
t.Run("create record", func(t *testing.T) {
|
||||
record := CreateRecord(ActionAllow, OperationGet)
|
||||
require.Equal(t, ActionAllow, record.Action())
|
||||
require.Equal(t, OperationGet, record.Operation())
|
||||
})
|
||||
|
||||
t.Run("new from nil v2 record", func(t *testing.T) {
|
||||
require.Equal(t, new(Record), NewRecordFromV2(nil))
|
||||
})
|
||||
}
|
||||
|
||||
func TestAddFormedTarget(t *testing.T) {
|
||||
items := []struct {
|
||||
role Role
|
||||
keys []ecdsa.PublicKey
|
||||
}{
|
||||
{
|
||||
role: RoleUnknown,
|
||||
keys: []ecdsa.PublicKey{*randomPublicKey(t)},
|
||||
},
|
||||
{
|
||||
role: RoleSystem,
|
||||
keys: []ecdsa.PublicKey{},
|
||||
},
|
||||
}
|
||||
|
||||
targets := make([]*Target, 0, len(items))
|
||||
|
||||
r := NewRecord()
|
||||
|
||||
for _, item := range items {
|
||||
tgt := NewTarget()
|
||||
tgt.SetRole(item.role)
|
||||
SetTargetECDSAKeys(tgt, ecdsaKeysToPtrs(item.keys)...)
|
||||
|
||||
targets = append(targets, tgt)
|
||||
|
||||
AddFormedTarget(r, item.role, item.keys...)
|
||||
}
|
||||
|
||||
tgts := r.Targets()
|
||||
require.Len(t, tgts, len(targets))
|
||||
|
||||
for _, tgt := range targets {
|
||||
require.Contains(t, tgts, tgt)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRecord_AddFilter(t *testing.T) {
|
||||
filters := []*Filter{
|
||||
newObjectFilter(MatchStringEqual, "some name", "ContainerID"),
|
||||
newObjectFilter(MatchStringNotEqual, "X-Header-Name", "X-Header-Value"),
|
||||
}
|
||||
|
||||
r := NewRecord()
|
||||
for _, filter := range filters {
|
||||
r.AddFilter(filter.From(), filter.Matcher(), filter.Key(), filter.Value())
|
||||
}
|
||||
|
||||
require.Equal(t, filters, r.Filters())
|
||||
}
|
||||
|
||||
func TestRecordEncoding(t *testing.T) {
|
||||
r := NewRecord()
|
||||
r.SetOperation(OperationHead)
|
||||
r.SetAction(ActionDeny)
|
||||
r.AddObjectAttributeFilter(MatchStringEqual, "key", "value")
|
||||
AddFormedTarget(r, RoleSystem, *randomPublicKey(t))
|
||||
|
||||
t.Run("binary", func(t *testing.T) {
|
||||
data, err := r.Marshal()
|
||||
require.NoError(t, err)
|
||||
|
||||
r2 := NewRecord()
|
||||
require.NoError(t, r2.Unmarshal(data))
|
||||
|
||||
require.Equal(t, r, r2)
|
||||
})
|
||||
|
||||
t.Run("json", func(t *testing.T) {
|
||||
data, err := r.MarshalJSON()
|
||||
require.NoError(t, err)
|
||||
|
||||
r2 := NewRecord()
|
||||
require.NoError(t, r2.UnmarshalJSON(data))
|
||||
|
||||
require.Equal(t, r, r2)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRecord_ToV2(t *testing.T) {
|
||||
t.Run("nil", func(t *testing.T) {
|
||||
var x *Record
|
||||
|
||||
require.Nil(t, x.ToV2())
|
||||
})
|
||||
|
||||
t.Run("default values", func(t *testing.T) {
|
||||
record := NewRecord()
|
||||
|
||||
// check initial values
|
||||
require.Equal(t, OperationUnknown, record.Operation())
|
||||
require.Equal(t, ActionUnknown, record.Action())
|
||||
require.Nil(t, record.Targets())
|
||||
require.Nil(t, record.Filters())
|
||||
|
||||
// convert to v2 message
|
||||
recordV2 := record.ToV2()
|
||||
|
||||
require.Equal(t, v2acl.OperationUnknown, recordV2.GetOperation())
|
||||
require.Equal(t, v2acl.ActionUnknown, recordV2.GetAction())
|
||||
require.Nil(t, recordV2.GetTargets())
|
||||
require.Nil(t, recordV2.GetFilters())
|
||||
})
|
||||
}
|
||||
|
||||
func TestReservedRecords(t *testing.T) {
|
||||
var (
|
||||
v = versiontest.Version()
|
||||
oid = objecttest.ID()
|
||||
cid = cidtest.GenerateID()
|
||||
ownerid = ownertest.GenerateID()
|
||||
h = checksumtest.Checksum()
|
||||
typ = new(object.Type)
|
||||
)
|
||||
|
||||
testSuit := []struct {
|
||||
f func(r *Record)
|
||||
key string
|
||||
value string
|
||||
}{
|
||||
{
|
||||
f: func(r *Record) { r.AddObjectAttributeFilter(MatchStringEqual, "foo", "bar") },
|
||||
key: "foo",
|
||||
value: "bar",
|
||||
},
|
||||
{
|
||||
f: func(r *Record) { r.AddObjectVersionFilter(MatchStringEqual, v) },
|
||||
key: v2acl.FilterObjectVersion,
|
||||
value: v.String(),
|
||||
},
|
||||
{
|
||||
f: func(r *Record) { r.AddObjectIDFilter(MatchStringEqual, oid) },
|
||||
key: v2acl.FilterObjectID,
|
||||
value: oid.String(),
|
||||
},
|
||||
{
|
||||
f: func(r *Record) { r.AddObjectContainerIDFilter(MatchStringEqual, cid) },
|
||||
key: v2acl.FilterObjectContainerID,
|
||||
value: cid.String(),
|
||||
},
|
||||
{
|
||||
f: func(r *Record) { r.AddObjectOwnerIDFilter(MatchStringEqual, ownerid) },
|
||||
key: v2acl.FilterObjectOwnerID,
|
||||
value: ownerid.String(),
|
||||
},
|
||||
{
|
||||
f: func(r *Record) { r.AddObjectCreationEpoch(MatchStringEqual, 100) },
|
||||
key: v2acl.FilterObjectCreationEpoch,
|
||||
value: "100",
|
||||
},
|
||||
{
|
||||
f: func(r *Record) { r.AddObjectPayloadLengthFilter(MatchStringEqual, 5000) },
|
||||
key: v2acl.FilterObjectPayloadLength,
|
||||
value: "5000",
|
||||
},
|
||||
{
|
||||
f: func(r *Record) { r.AddObjectPayloadHashFilter(MatchStringEqual, h) },
|
||||
key: v2acl.FilterObjectPayloadHash,
|
||||
value: h.String(),
|
||||
},
|
||||
{
|
||||
f: func(r *Record) { r.AddObjectHomomorphicHashFilter(MatchStringEqual, h) },
|
||||
key: v2acl.FilterObjectHomomorphicHash,
|
||||
value: h.String(),
|
||||
},
|
||||
{
|
||||
f: func(r *Record) {
|
||||
require.True(t, typ.FromString("REGULAR"))
|
||||
r.AddObjectTypeFilter(MatchStringEqual, *typ)
|
||||
},
|
||||
key: v2acl.FilterObjectType,
|
||||
value: "REGULAR",
|
||||
},
|
||||
{
|
||||
f: func(r *Record) {
|
||||
require.True(t, typ.FromString("TOMBSTONE"))
|
||||
r.AddObjectTypeFilter(MatchStringEqual, *typ)
|
||||
},
|
||||
key: v2acl.FilterObjectType,
|
||||
value: "TOMBSTONE",
|
||||
},
|
||||
{
|
||||
f: func(r *Record) {
|
||||
require.True(t, typ.FromString("STORAGE_GROUP"))
|
||||
r.AddObjectTypeFilter(MatchStringEqual, *typ)
|
||||
},
|
||||
key: v2acl.FilterObjectType,
|
||||
value: "STORAGE_GROUP",
|
||||
},
|
||||
}
|
||||
|
||||
for n, testCase := range testSuit {
|
||||
desc := fmt.Sprintf("case #%d", n)
|
||||
record := NewRecord()
|
||||
testCase.f(record)
|
||||
require.Len(t, record.Filters(), 1, desc)
|
||||
f := record.Filters()[0]
|
||||
require.Equal(t, f.Key(), testCase.key, desc)
|
||||
require.Equal(t, f.Value(), testCase.value, desc)
|
||||
}
|
||||
}
|
||||
|
||||
func randomPublicKey(t *testing.T) *ecdsa.PublicKey {
|
||||
p, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
return (*ecdsa.PublicKey)(p.PublicKey())
|
||||
}
|
201
eacl/table.go
Normal file
201
eacl/table.go
Normal file
|
@ -0,0 +1,201 @@
|
|||
package eacl
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
|
||||
v2acl "github.com/nspcc-dev/neofs-api-go/v2/acl"
|
||||
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/session"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/signature"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/version"
|
||||
)
|
||||
|
||||
// Table is a group of EACL records for single container.
|
||||
//
|
||||
// Table is compatible with v2 acl.EACLTable message.
|
||||
type Table struct {
|
||||
version version.Version
|
||||
cid *cid.ID
|
||||
token *session.Token
|
||||
sig *signature.Signature
|
||||
records []*Record
|
||||
}
|
||||
|
||||
// CID returns identifier of the container that should use given access control rules.
|
||||
func (t Table) CID() *cid.ID {
|
||||
return t.cid
|
||||
}
|
||||
|
||||
// SetCID sets identifier of the container that should use given access control rules.
|
||||
func (t *Table) SetCID(cid *cid.ID) {
|
||||
t.cid = cid
|
||||
}
|
||||
|
||||
// Version returns version of eACL format.
|
||||
func (t Table) Version() version.Version {
|
||||
return t.version
|
||||
}
|
||||
|
||||
// SetVersion sets version of eACL format.
|
||||
func (t *Table) SetVersion(version version.Version) {
|
||||
t.version = version
|
||||
}
|
||||
|
||||
// Records returns list of extended ACL rules.
|
||||
func (t Table) Records() []*Record {
|
||||
return t.records
|
||||
}
|
||||
|
||||
// AddRecord adds single eACL rule.
|
||||
func (t *Table) AddRecord(r *Record) {
|
||||
if r != nil {
|
||||
t.records = append(t.records, r)
|
||||
}
|
||||
}
|
||||
|
||||
// SessionToken returns token of the session
|
||||
// within which Table was set.
|
||||
func (t Table) SessionToken() *session.Token {
|
||||
return t.token
|
||||
}
|
||||
|
||||
// SetSessionToken sets token of the session
|
||||
// within which Table was set.
|
||||
func (t *Table) SetSessionToken(tok *session.Token) {
|
||||
t.token = tok
|
||||
}
|
||||
|
||||
// Signature returns Table signature.
|
||||
func (t Table) Signature() *signature.Signature {
|
||||
return t.sig
|
||||
}
|
||||
|
||||
// SetSignature sets Table signature.
|
||||
func (t *Table) SetSignature(sig *signature.Signature) {
|
||||
t.sig = sig
|
||||
}
|
||||
|
||||
// ToV2 converts Table to v2 acl.EACLTable message.
|
||||
//
|
||||
// Nil Table converts to nil.
|
||||
func (t *Table) ToV2() *v2acl.Table {
|
||||
if t == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
v2 := new(v2acl.Table)
|
||||
|
||||
if t.cid != nil {
|
||||
v2.SetContainerID(t.cid.ToV2())
|
||||
}
|
||||
|
||||
if t.records != nil {
|
||||
records := make([]*v2acl.Record, 0, len(t.records))
|
||||
for _, record := range t.records {
|
||||
records = append(records, record.ToV2())
|
||||
}
|
||||
|
||||
v2.SetRecords(records)
|
||||
}
|
||||
|
||||
v2.SetVersion(t.version.ToV2())
|
||||
|
||||
return v2
|
||||
}
|
||||
|
||||
// NewTable creates, initializes and returns blank Table instance.
|
||||
//
|
||||
// Defaults:
|
||||
// - version: version.Current();
|
||||
// - container ID: nil;
|
||||
// - records: nil;
|
||||
// - session token: nil;
|
||||
// - signature: nil.
|
||||
func NewTable() *Table {
|
||||
t := new(Table)
|
||||
t.SetVersion(*version.Current())
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
// CreateTable creates, initializes with parameters and returns Table instance.
|
||||
func CreateTable(cid cid.ID) *Table {
|
||||
t := NewTable()
|
||||
t.SetCID(&cid)
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
// NewTableFromV2 converts v2 acl.EACLTable message to Table.
|
||||
func NewTableFromV2(table *v2acl.Table) *Table {
|
||||
t := new(Table)
|
||||
|
||||
if table == nil {
|
||||
return t
|
||||
}
|
||||
|
||||
// set version
|
||||
if v := table.GetVersion(); v != nil {
|
||||
ver := version.Version{}
|
||||
ver.SetMajor(v.GetMajor())
|
||||
ver.SetMinor(v.GetMinor())
|
||||
|
||||
t.SetVersion(ver)
|
||||
}
|
||||
|
||||
// set container id
|
||||
if id := table.GetContainerID(); id != nil {
|
||||
if t.cid == nil {
|
||||
t.cid = new(cid.ID)
|
||||
}
|
||||
|
||||
var h [sha256.Size]byte
|
||||
|
||||
copy(h[:], id.GetValue())
|
||||
t.cid.SetSHA256(h)
|
||||
}
|
||||
|
||||
// set eacl records
|
||||
v2records := table.GetRecords()
|
||||
t.records = make([]*Record, 0, len(v2records))
|
||||
|
||||
for i := range v2records {
|
||||
t.records = append(t.records, NewRecordFromV2(v2records[i]))
|
||||
}
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
// Marshal marshals Table into a protobuf binary form.
|
||||
func (t *Table) Marshal() ([]byte, error) {
|
||||
return t.ToV2().StableMarshal(nil)
|
||||
}
|
||||
|
||||
// Unmarshal unmarshals protobuf binary representation of Table.
|
||||
func (t *Table) Unmarshal(data []byte) error {
|
||||
fV2 := new(v2acl.Table)
|
||||
if err := fV2.Unmarshal(data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*t = *NewTableFromV2(fV2)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON encodes Table to protobuf JSON format.
|
||||
func (t *Table) MarshalJSON() ([]byte, error) {
|
||||
return t.ToV2().MarshalJSON()
|
||||
}
|
||||
|
||||
// UnmarshalJSON decodes Table from protobuf JSON format.
|
||||
func (t *Table) UnmarshalJSON(data []byte) error {
|
||||
tV2 := new(v2acl.Table)
|
||||
if err := tV2.UnmarshalJSON(data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*t = *NewTableFromV2(tV2)
|
||||
|
||||
return nil
|
||||
}
|
137
eacl/table_test.go
Normal file
137
eacl/table_test.go
Normal file
|
@ -0,0 +1,137 @@
|
|||
package eacl_test
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"testing"
|
||||
|
||||
cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/eacl"
|
||||
eacltest "github.com/nspcc-dev/neofs-sdk-go/eacl/test"
|
||||
sessiontest "github.com/nspcc-dev/neofs-sdk-go/session/test"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/signature"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/version"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestTable(t *testing.T) {
|
||||
var v version.Version
|
||||
|
||||
sha := sha256.Sum256([]byte("container id"))
|
||||
id := cidtest.GenerateIDWithChecksum(sha)
|
||||
|
||||
v.SetMajor(3)
|
||||
v.SetMinor(2)
|
||||
|
||||
table := eacl.NewTable()
|
||||
table.SetVersion(v)
|
||||
table.SetCID(id)
|
||||
table.AddRecord(eacl.CreateRecord(eacl.ActionAllow, eacl.OperationPut))
|
||||
|
||||
v2 := table.ToV2()
|
||||
require.NotNil(t, v2)
|
||||
require.Equal(t, uint32(3), v2.GetVersion().GetMajor())
|
||||
require.Equal(t, uint32(2), v2.GetVersion().GetMinor())
|
||||
require.Equal(t, sha[:], v2.GetContainerID().GetValue())
|
||||
require.Len(t, v2.GetRecords(), 1)
|
||||
|
||||
newTable := eacl.NewTableFromV2(v2)
|
||||
require.Equal(t, table, newTable)
|
||||
|
||||
t.Run("new from nil v2 table", func(t *testing.T) {
|
||||
require.Equal(t, new(eacl.Table), eacl.NewTableFromV2(nil))
|
||||
})
|
||||
|
||||
t.Run("create table", func(t *testing.T) {
|
||||
id := cidtest.GenerateID()
|
||||
|
||||
table := eacl.CreateTable(*id)
|
||||
require.Equal(t, id, table.CID())
|
||||
require.Equal(t, *version.Current(), table.Version())
|
||||
})
|
||||
}
|
||||
|
||||
func TestTable_AddRecord(t *testing.T) {
|
||||
records := []*eacl.Record{
|
||||
eacl.CreateRecord(eacl.ActionDeny, eacl.OperationDelete),
|
||||
eacl.CreateRecord(eacl.ActionAllow, eacl.OperationPut),
|
||||
}
|
||||
|
||||
table := eacl.NewTable()
|
||||
for _, record := range records {
|
||||
table.AddRecord(record)
|
||||
}
|
||||
|
||||
require.Equal(t, records, table.Records())
|
||||
}
|
||||
|
||||
func TestTableEncoding(t *testing.T) {
|
||||
tab := eacltest.Table()
|
||||
|
||||
t.Run("binary", func(t *testing.T) {
|
||||
data, err := tab.Marshal()
|
||||
require.NoError(t, err)
|
||||
|
||||
tab2 := eacl.NewTable()
|
||||
require.NoError(t, tab2.Unmarshal(data))
|
||||
|
||||
// FIXME: we compare v2 messages because
|
||||
// Filter contains fmt.Stringer interface
|
||||
require.Equal(t, tab.ToV2(), tab2.ToV2())
|
||||
})
|
||||
|
||||
t.Run("json", func(t *testing.T) {
|
||||
data, err := tab.MarshalJSON()
|
||||
require.NoError(t, err)
|
||||
|
||||
tab2 := eacl.NewTable()
|
||||
require.NoError(t, tab2.UnmarshalJSON(data))
|
||||
|
||||
require.Equal(t, tab.ToV2(), tab2.ToV2())
|
||||
})
|
||||
}
|
||||
|
||||
func TestTable_SessionToken(t *testing.T) {
|
||||
tok := sessiontest.Generate()
|
||||
|
||||
table := eacl.NewTable()
|
||||
table.SetSessionToken(tok)
|
||||
|
||||
require.Equal(t, tok, table.SessionToken())
|
||||
}
|
||||
|
||||
func TestTable_Signature(t *testing.T) {
|
||||
sig := signature.New()
|
||||
sig.SetKey([]byte{1, 2, 3})
|
||||
sig.SetSign([]byte{4, 5, 6})
|
||||
|
||||
table := eacl.NewTable()
|
||||
table.SetSignature(sig)
|
||||
|
||||
require.Equal(t, sig, table.Signature())
|
||||
}
|
||||
|
||||
func TestTable_ToV2(t *testing.T) {
|
||||
t.Run("nil", func(t *testing.T) {
|
||||
var x *eacl.Table
|
||||
|
||||
require.Nil(t, x.ToV2())
|
||||
})
|
||||
|
||||
t.Run("default values", func(t *testing.T) {
|
||||
table := eacl.NewTable()
|
||||
|
||||
// check initial values
|
||||
require.Equal(t, *version.Current(), table.Version())
|
||||
require.Nil(t, table.Records())
|
||||
require.Nil(t, table.CID())
|
||||
require.Nil(t, table.SessionToken())
|
||||
require.Nil(t, table.Signature())
|
||||
|
||||
// convert to v2 message
|
||||
tableV2 := table.ToV2()
|
||||
|
||||
require.Equal(t, version.Current().ToV2(), tableV2.GetVersion())
|
||||
require.Nil(t, tableV2.GetRecords())
|
||||
require.Nil(t, tableV2.GetContainerID())
|
||||
})
|
||||
}
|
157
eacl/target.go
Normal file
157
eacl/target.go
Normal file
|
@ -0,0 +1,157 @@
|
|||
package eacl
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
v2acl "github.com/nspcc-dev/neofs-api-go/v2/acl"
|
||||
)
|
||||
|
||||
// Target is a group of request senders to match EACL. Defined by role enum
|
||||
// and set of public keys.
|
||||
//
|
||||
// Target is compatible with v2 acl.EACLRecord.Target message.
|
||||
type Target struct {
|
||||
role Role
|
||||
keys [][]byte
|
||||
}
|
||||
|
||||
func ecdsaKeysToPtrs(keys []ecdsa.PublicKey) []*ecdsa.PublicKey {
|
||||
keysPtr := make([]*ecdsa.PublicKey, len(keys))
|
||||
|
||||
for i := range keys {
|
||||
keysPtr[i] = &keys[i]
|
||||
}
|
||||
|
||||
return keysPtr
|
||||
}
|
||||
|
||||
// BinaryKeys returns list of public keys to identify
|
||||
// target subject in a binary format.
|
||||
func (t *Target) BinaryKeys() [][]byte {
|
||||
return t.keys
|
||||
}
|
||||
|
||||
// SetBinaryKeys sets list of binary public keys to identify
|
||||
// target subject.
|
||||
func (t *Target) SetBinaryKeys(keys [][]byte) {
|
||||
t.keys = keys
|
||||
}
|
||||
|
||||
// SetTargetECDSAKeys converts ECDSA public keys to a binary
|
||||
// format and stores them in Target.
|
||||
func SetTargetECDSAKeys(t *Target, pubs ...*ecdsa.PublicKey) {
|
||||
binKeys := t.BinaryKeys()
|
||||
ln := len(pubs)
|
||||
|
||||
if cap(binKeys) >= ln {
|
||||
binKeys = binKeys[:0]
|
||||
} else {
|
||||
binKeys = make([][]byte, 0, ln)
|
||||
}
|
||||
|
||||
for i := 0; i < ln; i++ {
|
||||
binKeys = append(binKeys, (*keys.PublicKey)(pubs[i]).Bytes())
|
||||
}
|
||||
|
||||
t.SetBinaryKeys(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.
|
||||
func TargetECDSAKeys(t *Target) []*ecdsa.PublicKey {
|
||||
binKeys := t.BinaryKeys()
|
||||
ln := len(binKeys)
|
||||
|
||||
pubs := make([]*ecdsa.PublicKey, ln)
|
||||
|
||||
for i := 0; i < ln; i++ {
|
||||
p := new(keys.PublicKey)
|
||||
if p.DecodeBytes(binKeys[i]) == nil {
|
||||
pubs[i] = (*ecdsa.PublicKey)(p)
|
||||
}
|
||||
}
|
||||
|
||||
return pubs
|
||||
}
|
||||
|
||||
// SetRole sets target subject's role class.
|
||||
func (t *Target) SetRole(r Role) {
|
||||
t.role = r
|
||||
}
|
||||
|
||||
// Role returns target subject's role class.
|
||||
func (t Target) Role() Role {
|
||||
return t.role
|
||||
}
|
||||
|
||||
// ToV2 converts Target to v2 acl.EACLRecord.Target message.
|
||||
//
|
||||
// Nil Target converts to nil.
|
||||
func (t *Target) ToV2() *v2acl.Target {
|
||||
if t == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
target := new(v2acl.Target)
|
||||
target.SetRole(t.role.ToV2())
|
||||
target.SetKeys(t.keys)
|
||||
|
||||
return target
|
||||
}
|
||||
|
||||
// NewTarget creates, initializes and returns blank Target instance.
|
||||
//
|
||||
// Defaults:
|
||||
// - role: RoleUnknown;
|
||||
// - keys: nil.
|
||||
func NewTarget() *Target {
|
||||
return NewTargetFromV2(new(v2acl.Target))
|
||||
}
|
||||
|
||||
// NewTargetFromV2 converts v2 acl.EACLRecord.Target message to Target.
|
||||
func NewTargetFromV2(target *v2acl.Target) *Target {
|
||||
if target == nil {
|
||||
return new(Target)
|
||||
}
|
||||
|
||||
return &Target{
|
||||
role: RoleFromV2(target.GetRole()),
|
||||
keys: target.GetKeys(),
|
||||
}
|
||||
}
|
||||
|
||||
// Marshal marshals Target into a protobuf binary form.
|
||||
func (t *Target) Marshal() ([]byte, error) {
|
||||
return t.ToV2().StableMarshal(nil)
|
||||
}
|
||||
|
||||
// Unmarshal unmarshals protobuf binary representation of Target.
|
||||
func (t *Target) Unmarshal(data []byte) error {
|
||||
fV2 := new(v2acl.Target)
|
||||
if err := fV2.Unmarshal(data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*t = *NewTargetFromV2(fV2)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON encodes Target to protobuf JSON format.
|
||||
func (t *Target) MarshalJSON() ([]byte, error) {
|
||||
return t.ToV2().MarshalJSON()
|
||||
}
|
||||
|
||||
// UnmarshalJSON decodes Target from protobuf JSON format.
|
||||
func (t *Target) UnmarshalJSON(data []byte) error {
|
||||
tV2 := new(v2acl.Target)
|
||||
if err := tV2.UnmarshalJSON(data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*t = *NewTargetFromV2(tV2)
|
||||
|
||||
return nil
|
||||
}
|
85
eacl/target_test.go
Normal file
85
eacl/target_test.go
Normal file
|
@ -0,0 +1,85 @@
|
|||
package eacl
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/acl"
|
||||
v2acl "github.com/nspcc-dev/neofs-api-go/v2/acl"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestTarget(t *testing.T) {
|
||||
pubs := []*ecdsa.PublicKey{
|
||||
randomPublicKey(t),
|
||||
randomPublicKey(t),
|
||||
}
|
||||
|
||||
target := NewTarget()
|
||||
target.SetRole(RoleSystem)
|
||||
SetTargetECDSAKeys(target, pubs...)
|
||||
|
||||
v2 := target.ToV2()
|
||||
require.NotNil(t, v2)
|
||||
require.Equal(t, v2acl.RoleSystem, v2.GetRole())
|
||||
require.Len(t, v2.GetKeys(), len(pubs))
|
||||
for i, key := range v2.GetKeys() {
|
||||
require.Equal(t, key, (*keys.PublicKey)(pubs[i]).Bytes())
|
||||
}
|
||||
|
||||
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)
|
||||
SetTargetECDSAKeys(tar, randomPublicKey(t))
|
||||
|
||||
t.Run("binary", func(t *testing.T) {
|
||||
data, err := tar.Marshal()
|
||||
require.NoError(t, err)
|
||||
|
||||
tar2 := NewTarget()
|
||||
require.NoError(t, tar2.Unmarshal(data))
|
||||
|
||||
require.Equal(t, tar, tar2)
|
||||
})
|
||||
|
||||
t.Run("json", func(t *testing.T) {
|
||||
data, err := tar.MarshalJSON()
|
||||
require.NoError(t, err)
|
||||
|
||||
tar2 := NewTarget()
|
||||
require.NoError(t, tar2.UnmarshalJSON(data))
|
||||
|
||||
require.Equal(t, tar, tar2)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTarget_ToV2(t *testing.T) {
|
||||
t.Run("nil", func(t *testing.T) {
|
||||
var x *Target
|
||||
|
||||
require.Nil(t, x.ToV2())
|
||||
})
|
||||
|
||||
t.Run("default values", func(t *testing.T) {
|
||||
target := NewTarget()
|
||||
|
||||
// check initial values
|
||||
require.Equal(t, RoleUnknown, target.Role())
|
||||
require.Nil(t, target.BinaryKeys())
|
||||
|
||||
// convert to v2 message
|
||||
targetV2 := target.ToV2()
|
||||
|
||||
require.Equal(t, acl.RoleUnknown, targetV2.GetRole())
|
||||
require.Nil(t, targetV2.GetKeys())
|
||||
})
|
||||
}
|
45
eacl/test/generate.go
Normal file
45
eacl/test/generate.go
Normal file
|
@ -0,0 +1,45 @@
|
|||
package eacltest
|
||||
|
||||
import (
|
||||
cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/eacl"
|
||||
ownertest "github.com/nspcc-dev/neofs-sdk-go/owner/test"
|
||||
versiontest "github.com/nspcc-dev/neofs-sdk-go/version/test"
|
||||
)
|
||||
|
||||
// Target returns random eacl.Target.
|
||||
func Target() *eacl.Target {
|
||||
x := eacl.NewTarget()
|
||||
|
||||
x.SetRole(eacl.RoleSystem)
|
||||
x.SetBinaryKeys([][]byte{
|
||||
{1, 2, 3},
|
||||
{4, 5, 6},
|
||||
})
|
||||
|
||||
return x
|
||||
}
|
||||
|
||||
// Record returns random eacl.Record.
|
||||
func Record() *eacl.Record {
|
||||
x := eacl.NewRecord()
|
||||
|
||||
x.SetAction(eacl.ActionAllow)
|
||||
x.SetOperation(eacl.OperationRangeHash)
|
||||
x.SetTargets(Target(), Target())
|
||||
x.AddObjectContainerIDFilter(eacl.MatchStringEqual, cidtest.GenerateID())
|
||||
x.AddObjectOwnerIDFilter(eacl.MatchStringNotEqual, ownertest.GenerateID())
|
||||
|
||||
return x
|
||||
}
|
||||
|
||||
func Table() *eacl.Table {
|
||||
x := eacl.NewTable()
|
||||
|
||||
x.SetCID(cidtest.GenerateID())
|
||||
x.AddRecord(Record())
|
||||
x.AddRecord(Record())
|
||||
x.SetVersion(*versiontest.Version())
|
||||
|
||||
return x
|
||||
}
|
Loading…
Reference in a new issue