[#225] container: Replace basic ACL code to a separate package

Create `acl` package inside `container` path. Replace basic ACL
functionality into it.

Signed-off-by: Leonard Lyubich <leonard@nspcc.ru>
This commit is contained in:
Leonard Lyubich 2022-06-20 18:28:27 +03:00 committed by fyrchik
parent e82a2d86ef
commit c4ebe8d854
14 changed files with 524 additions and 499 deletions

View file

@ -1,81 +0,0 @@
package container
import "strconv"
// ACLOp enumerates operations under access control inside container.
// Non-positive values are reserved and depend on context (e.g. unsupported op).
//
// Note that type conversion from- and to numerical types is not recommended,
// use corresponding constants and/or methods instead.
type ACLOp uint32
const (
aclOpZero ACLOp = iota // extreme value for testing
ACLOpObjectGet // Object.Get rpc
ACLOpObjectHead // Object.Head rpc
ACLOpObjectPut // Object.Put rpc
ACLOpObjectDelete // Object.Delete rpc
ACLOpObjectSearch // Object.Search rpc
ACLOpObjectRange // Object.GetRange rpc
ACLOpObjectHash // Object.GetRangeHash rpc
aclOpLast // extreme value for testing
)
// String implements fmt.Stringer.
func (x ACLOp) String() string {
switch x {
default:
return "UNKNOWN#" + strconv.FormatUint(uint64(x), 10)
case ACLOpObjectGet:
return "OBJECT_GET"
case ACLOpObjectHead:
return "OBJECT_HEAD"
case ACLOpObjectPut:
return "OBJECT_PUT"
case ACLOpObjectDelete:
return "OBJECT_DELETE"
case ACLOpObjectSearch:
return "OBJECT_SEARCH"
case ACLOpObjectRange:
return "OBJECT_RANGE"
case ACLOpObjectHash:
return "OBJECT_HASH"
}
}
// ACLRole enumerates roles covered by container ACL. Each role represents
// some party which can be authenticated during container op execution.
// Non-positive values are reserved and depend on context (e.g. unsupported role).
//
// Note that type conversion from- and to numerical types is not recommended,
// use corresponding constants and/or methods instead.
type ACLRole uint32
const (
aclRoleZero ACLRole = iota // extreme value for testing
ACLRoleOwner // container owner
ACLRoleContainer // nodes of the related container
ACLRoleInnerRing // Inner Ring nodes
ACLRoleOthers // all others
aclRoleLast // extreme value for testing
)
// String implements fmt.Stringer.
func (x ACLRole) String() string {
switch x {
default:
return "UNKNOWN#" + strconv.FormatUint(uint64(x), 10)
case ACLRoleOwner:
return "OWNER"
case ACLRoleContainer:
return "CONTAINER"
case ACLRoleInnerRing:
return "INNER_RING"
case ACLRoleOthers:
return "OTHERS"
}
}

81
container/acl/acl.go Normal file
View file

@ -0,0 +1,81 @@
package acl
import "strconv"
// Op enumerates operations under access control inside container.
// Non-positive values are reserved and depend on context (e.g. unsupported op).
//
// Note that type conversion from- and to numerical types is not recommended,
// use corresponding constants and/or methods instead.
type Op uint32
const (
opZero Op = iota // extreme value for testing
OpObjectGet // Object.Get rpc
OpObjectHead // Object.Head rpc
OpObjectPut // Object.Put rpc
OpObjectDelete // Object.Delete rpc
OpObjectSearch // Object.Search rpc
OpObjectRange // Object.GetRange rpc
OpObjectHash // Object.GetRangeHash rpc
opLast // extreme value for testing
)
// String implements fmt.Stringer.
func (x Op) String() string {
switch x {
default:
return "UNKNOWN#" + strconv.FormatUint(uint64(x), 10)
case OpObjectGet:
return "OBJECT_GET"
case OpObjectHead:
return "OBJECT_HEAD"
case OpObjectPut:
return "OBJECT_PUT"
case OpObjectDelete:
return "OBJECT_DELETE"
case OpObjectSearch:
return "OBJECT_SEARCH"
case OpObjectRange:
return "OBJECT_RANGE"
case OpObjectHash:
return "OBJECT_HASH"
}
}
// Role enumerates roles covered by container ACL. Each role represents
// some party which can be authenticated during container op execution.
// Non-positive values are reserved and depend on context (e.g. unsupported role).
//
// Note that type conversion from- and to numerical types is not recommended,
// use corresponding constants and/or methods instead.
type Role uint32
const (
roleZero Role = iota // extreme value for testing
RoleOwner // container owner
RoleContainer // nodes of the related container
RoleInnerRing // Inner Ring nodes
RoleOthers // all others
roleLast // extreme value for testing
)
// String implements fmt.Stringer.
func (x Role) String() string {
switch x {
default:
return "UNKNOWN#" + strconv.FormatUint(uint64(x), 10)
case RoleOwner:
return "OWNER"
case RoleContainer:
return "CONTAINER"
case RoleInnerRing:
return "INNER_RING"
case RoleOthers:
return "OTHERS"
}
}

278
container/acl/acl_basic.go Normal file
View file

@ -0,0 +1,278 @@
package acl
import (
"fmt"
"strconv"
"strings"
)
// Basic represents basic part of the NeoFS container's ACL. It includes
// common (pretty simple) access rules for operations inside the container.
// See NeoFS Specification for details.
//
// One can find some similarities with the traditional Unix permission, such as
// division into scopes: user, group, others
// op-permissions: read, write, etc.
// sticky bit
// However, these similarities should only be used for better understanding,
// in general these mechanisms are different.
//
// Instances can be created using built-in var declaration, but look carefully
// at the default values, and how individual permissions are regulated.
// Some frequently used values are presented in exported variables.
//
// Basic instances are comparable: values can be compared directly using
// == operator.
type Basic struct {
bits uint32
}
// FromBits decodes Basic from the numerical representation.
//
// See also Bits.
func (x *Basic) FromBits(bits uint32) {
x.bits = bits
}
// Bits returns numerical encoding of Basic.
//
// See also FromBits.
func (x Basic) Bits() uint32 {
return x.bits
}
// common bit sections.
const (
opAmount = 7
bitsPerOp = 4
bitPosFinal = opAmount * bitsPerOp
bitPosSticky = bitPosFinal + 1
)
// per-op bit order.
const (
opBitPosBearer uint8 = iota
opBitPosOthers
opBitPosContainer
opBitPosOwner
)
// DisableExtension makes Basic FINAL. FINAL indicates the ACL non-extendability
// in the related container.
//
// See also Extendable.
func (x *Basic) DisableExtension() {
setBit(&x.bits, bitPosFinal)
}
// Extendable checks if Basic is NOT made FINAL using DisableExtension.
//
// Zero Basic is NOT FINAL or extendable.
func (x Basic) Extendable() bool {
return !isBitSet(x.bits, bitPosFinal)
}
// MakeSticky makes Basic STICKY. STICKY indicates that only the owner of any
// particular object is allowed to operate on it.
//
// See also Sticky.
func (x *Basic) MakeSticky() {
setBit(&x.bits, bitPosSticky)
}
// Sticky checks if Basic is made STICKY using MakeSticky.
//
// Zero Basic is NOT STICKY.
func (x Basic) Sticky() bool {
return isBitSet(x.bits, bitPosSticky)
}
// checks if op is used by the storage nodes within replication mechanism.
func isReplicationOp(op Op) bool {
//nolint:exhaustive
switch op {
case
OpObjectGet,
OpObjectHead,
OpObjectPut,
OpObjectSearch,
OpObjectHash:
return true
}
return false
}
// AllowOp allows the parties with the given role to the given operation.
// Op MUST be one of the Op enumeration. Role MUST be one of:
// RoleOwner
// RoleContainer
// RoleOthers
// and if role is RoleContainer, op MUST NOT be:
// OpObjectGet
// OpObjectHead
// OpObjectPut
// OpObjectSearch
// OpObjectHash
//
// See also IsOpAllowed.
func (x *Basic) AllowOp(op Op, role Role) {
var bitPos uint8
switch role {
default:
panic(fmt.Sprintf("unable to set rules for unsupported role %v", role))
case RoleInnerRing:
panic("basic ACL MUST NOT be modified for Inner Ring")
case RoleOwner:
bitPos = opBitPosOwner
case RoleContainer:
if isReplicationOp(op) {
panic("basic ACL for container replication ops MUST NOT be modified")
}
bitPos = opBitPosContainer
case RoleOthers:
bitPos = opBitPosOthers
}
setOpBit(&x.bits, op, bitPos)
}
// IsOpAllowed checks if parties with the given role are allowed to the given op
// according to the Basic rules. Op MUST be one of the Op enumeration.
// Role MUST be one of the Role enumeration.
//
// Members with RoleContainer role have exclusive default access to the
// operations of the data replication mechanism:
// OpObjectGet
// OpObjectHead
// OpObjectPut
// OpObjectSearch
// OpObjectHash
//
// RoleInnerRing members are allowed to data audit ops only:
// OpObjectGet
// OpObjectHead
// OpObjectHash
// OpObjectSearch
//
// Zero Basic prevents any role from accessing any operation in the absence
// of default rights.
//
// See also AllowOp.
func (x Basic) IsOpAllowed(op Op, role Role) bool {
var bitPos uint8
switch role {
default:
panic(fmt.Sprintf("role is unsupported %v", role))
case RoleInnerRing:
switch op {
case
OpObjectGet,
OpObjectHead,
OpObjectHash,
OpObjectSearch:
return true
default:
return false
}
case RoleOwner:
bitPos = opBitPosOwner
case RoleContainer:
if isReplicationOp(op) {
return true
}
bitPos = opBitPosContainer
case RoleOthers:
bitPos = opBitPosOthers
}
return isOpBitSet(x.bits, op, bitPos)
}
// AllowBearerRules allows bearer to provide extended ACL rules for the given
// operation. Bearer rules doesn't depend on container ACL
// // extensibility.
//
// See also AllowedBearerRules.
func (x *Basic) AllowBearerRules(op Op) {
setOpBit(&x.bits, op, opBitPosBearer)
}
// AllowedBearerRules checks if bearer rules are allowed using AllowBearerRules.
// Op MUST be one of the Op enumeration.
//
// Zero Basic disallows bearer rules for any op.
func (x Basic) AllowedBearerRules(op Op) bool {
return isOpBitSet(x.bits, op, opBitPosBearer)
}
// EncodeToString encodes Basic into hexadecimal string.
//
// See also DecodeString.
func (x Basic) EncodeToString() string {
return strconv.FormatUint(uint64(x.bits), 16)
}
// Names of the frequently used Basic values.
const (
NamePrivate = "private"
NamePrivateExtended = "eacl-private"
NamePublicRO = "public-read"
NamePublicROExtended = "eacl-public-read"
NamePublicRW = "public-read-write"
NamePublicRWExtended = "eacl-public-read-write"
NamePublicAppend = "public-append"
NamePublicAppendExtended = "eacl-public-append"
)
// Frequently used Basic values (each value MUST NOT be modified, make a
// copy instead).
var (
Private Basic // private
PrivateExtended Basic // eacl-private
PublicRO Basic // public-read
PublicROExtended Basic // eacl-public-read
PublicRW Basic // public-read-write
PublicRWExtended Basic // eacl-public-read-write
PublicAppend Basic // public-append
PublicAppendExtended Basic // eacl-public-append
)
// DecodeString decodes string calculated using EncodeToString. Also supports
// human-readable names (Name* constants).
func (x *Basic) DecodeString(s string) (e error) {
switch s {
case NamePrivate:
*x = Private
case NamePrivateExtended:
*x = PrivateExtended
case NamePublicRO:
*x = PublicRO
case NamePublicROExtended:
*x = PublicROExtended
case NamePublicRW:
*x = PublicRW
case NamePublicRWExtended:
*x = PublicRWExtended
case NamePublicAppend:
*x = PublicAppend
case NamePublicAppendExtended:
*x = PublicAppendExtended
default:
s = strings.TrimPrefix(strings.ToLower(s), "0x")
v, err := strconv.ParseUint(s, 16, 32)
if err != nil {
return fmt.Errorf("parse hex: %w", err)
}
x.bits = uint32(v)
}
return nil
}

View file

@ -1,4 +1,4 @@
package container
package acl
import (
"testing"
@ -6,51 +6,51 @@ import (
"github.com/stretchr/testify/require"
)
func TestBasicACL_DisableExtension(t *testing.T) {
var val, val2 BasicACL
func TestBasic_DisableExtension(t *testing.T) {
var val, val2 Basic
require.True(t, val.Extendable())
val2.fromUint32(val.toUint32())
val2.FromBits(val.Bits())
require.True(t, val2.Extendable())
val.DisableExtension()
require.False(t, val.Extendable())
val2.fromUint32(val.toUint32())
val2.FromBits(val.Bits())
require.False(t, val2.Extendable())
}
func TestBasicACL_MakeSticky(t *testing.T) {
var val, val2 BasicACL
func TestBasic_MakeSticky(t *testing.T) {
var val, val2 Basic
require.False(t, val.Sticky())
val2.fromUint32(val.toUint32())
val2.FromBits(val.Bits())
require.False(t, val2.Sticky())
val.MakeSticky()
require.True(t, val.Sticky())
val2.fromUint32(val.toUint32())
val2.FromBits(val.Bits())
require.True(t, val2.Sticky())
}
func TestBasicACL_AllowBearerRules(t *testing.T) {
var val BasicACL
func TestBasic_AllowBearerRules(t *testing.T) {
var val Basic
require.Panics(t, func() { val.AllowBearerRules(aclOpZero) })
require.Panics(t, func() { val.AllowBearerRules(aclOpLast) })
require.Panics(t, func() { val.AllowBearerRules(opZero) })
require.Panics(t, func() { val.AllowBearerRules(opLast) })
require.Panics(t, func() { val.AllowedBearerRules(aclOpZero) })
require.Panics(t, func() { val.AllowedBearerRules(aclOpLast) })
require.Panics(t, func() { val.AllowedBearerRules(opZero) })
require.Panics(t, func() { val.AllowedBearerRules(opLast) })
for op := aclOpZero + 1; op < aclOpLast; op++ {
for op := opZero + 1; op < opLast; op++ {
val := val
require.False(t, val.AllowedBearerRules(op))
val.AllowBearerRules(op)
for j := aclOpZero + 1; j < aclOpLast; j++ {
for j := opZero + 1; j < opLast; j++ {
if j == op {
require.True(t, val.AllowedBearerRules(j), op)
} else {
@ -60,39 +60,39 @@ func TestBasicACL_AllowBearerRules(t *testing.T) {
}
}
func TestBasicACL_AllowOp(t *testing.T) {
var val, val2 BasicACL
func TestBasic_AllowOp(t *testing.T) {
var val, val2 Basic
require.Panics(t, func() { val.IsOpAllowed(aclOpZero, aclRoleZero+1) })
require.Panics(t, func() { val.IsOpAllowed(aclOpLast, aclRoleZero+1) })
require.Panics(t, func() { val.IsOpAllowed(aclOpZero+1, aclRoleZero) })
require.Panics(t, func() { val.IsOpAllowed(aclOpZero+1, aclRoleLast) })
require.Panics(t, func() { val.IsOpAllowed(opZero, roleZero+1) })
require.Panics(t, func() { val.IsOpAllowed(opLast, roleZero+1) })
require.Panics(t, func() { val.IsOpAllowed(opZero+1, roleZero) })
require.Panics(t, func() { val.IsOpAllowed(opZero+1, roleLast) })
for op := aclOpZero + 1; op < aclOpLast; op++ {
require.Panics(t, func() { val.AllowOp(op, ACLRoleInnerRing) })
for op := opZero + 1; op < opLast; op++ {
require.Panics(t, func() { val.AllowOp(op, RoleInnerRing) })
if isReplicationOp(op) {
require.Panics(t, func() { val.AllowOp(op, ACLRoleContainer) })
require.True(t, val.IsOpAllowed(op, ACLRoleContainer))
require.Panics(t, func() { val.AllowOp(op, RoleContainer) })
require.True(t, val.IsOpAllowed(op, RoleContainer))
}
}
require.True(t, val.IsOpAllowed(ACLOpObjectGet, ACLRoleInnerRing))
require.True(t, val.IsOpAllowed(ACLOpObjectHead, ACLRoleInnerRing))
require.True(t, val.IsOpAllowed(ACLOpObjectSearch, ACLRoleInnerRing))
require.True(t, val.IsOpAllowed(ACLOpObjectHash, ACLRoleInnerRing))
require.True(t, val.IsOpAllowed(OpObjectGet, RoleInnerRing))
require.True(t, val.IsOpAllowed(OpObjectHead, RoleInnerRing))
require.True(t, val.IsOpAllowed(OpObjectSearch, RoleInnerRing))
require.True(t, val.IsOpAllowed(OpObjectHash, RoleInnerRing))
const op = aclOpZero + 1
const role = ACLRoleOthers
const op = opZero + 1
const role = RoleOthers
require.False(t, val.IsOpAllowed(op, role))
val2.fromUint32(val.toUint32())
val2.FromBits(val.Bits())
require.False(t, val2.IsOpAllowed(op, role))
val.AllowOp(op, role)
require.True(t, val.IsOpAllowed(op, role))
val2.fromUint32(val.toUint32())
val2.FromBits(val.Bits())
require.True(t, val2.IsOpAllowed(op, role))
}
@ -100,21 +100,21 @@ type opsExpected struct {
owner, container, innerRing, others, bearer bool
}
func testOp(t *testing.T, v BasicACL, op ACLOp, exp opsExpected) {
require.Equal(t, exp.owner, v.IsOpAllowed(op, ACLRoleOwner), op)
require.Equal(t, exp.container, v.IsOpAllowed(op, ACLRoleContainer), op)
require.Equal(t, exp.innerRing, v.IsOpAllowed(op, ACLRoleInnerRing), op)
require.Equal(t, exp.others, v.IsOpAllowed(op, ACLRoleOthers), op)
func testOp(t *testing.T, v Basic, op Op, exp opsExpected) {
require.Equal(t, exp.owner, v.IsOpAllowed(op, RoleOwner), op)
require.Equal(t, exp.container, v.IsOpAllowed(op, RoleContainer), op)
require.Equal(t, exp.innerRing, v.IsOpAllowed(op, RoleInnerRing), op)
require.Equal(t, exp.others, v.IsOpAllowed(op, RoleOthers), op)
require.Equal(t, exp.bearer, v.AllowedBearerRules(op), op)
}
type expected struct {
extendable, sticky bool
mOps map[ACLOp]opsExpected
mOps map[Op]opsExpected
}
func testBasicACLPredefined(t *testing.T, val BasicACL, name string, exp expected) {
func testBasicPredefined(t *testing.T, val Basic, name string, exp expected) {
require.Equal(t, exp.sticky, val.Sticky())
require.Equal(t, exp.extendable, val.Extendable())
@ -124,7 +124,7 @@ func testBasicACLPredefined(t *testing.T, val BasicACL, name string, exp expecte
s := val.EncodeToString()
var val2 BasicACL
var val2 Basic
require.NoError(t, val2.DecodeString(s))
require.Equal(t, val, val2)
@ -133,55 +133,55 @@ func testBasicACLPredefined(t *testing.T, val BasicACL, name string, exp expecte
require.Equal(t, val, val2)
}
func TestBasicACLPredefined(t *testing.T) {
func TestBasicPredefined(t *testing.T) {
t.Run("private", func(t *testing.T) {
exp := expected{
extendable: false,
sticky: false,
mOps: map[ACLOp]opsExpected{
ACLOpObjectHash: {
mOps: map[Op]opsExpected{
OpObjectHash: {
owner: true,
container: true,
innerRing: true,
others: false,
bearer: false,
},
ACLOpObjectRange: {
OpObjectRange: {
owner: true,
container: false,
innerRing: false,
others: false,
bearer: false,
},
ACLOpObjectSearch: {
OpObjectSearch: {
owner: true,
container: true,
innerRing: true,
others: false,
bearer: false,
},
ACLOpObjectDelete: {
OpObjectDelete: {
owner: true,
container: false,
innerRing: false,
others: false,
bearer: false,
},
ACLOpObjectPut: {
OpObjectPut: {
owner: true,
container: true,
innerRing: false,
others: false,
bearer: false,
},
ACLOpObjectHead: {
OpObjectHead: {
owner: true,
container: true,
innerRing: true,
others: false,
bearer: false,
},
ACLOpObjectGet: {
OpObjectGet: {
owner: true,
container: true,
innerRing: true,
@ -191,59 +191,59 @@ func TestBasicACLPredefined(t *testing.T) {
},
}
testBasicACLPredefined(t, BasicACLPrivate, BasicACLNamePrivate, exp)
testBasicPredefined(t, Private, NamePrivate, exp)
exp.extendable = true
testBasicACLPredefined(t, BasicACLPrivateExtended, BasicACLNamePrivateExtended, exp)
testBasicPredefined(t, PrivateExtended, NamePrivateExtended, exp)
})
t.Run("public-read", func(t *testing.T) {
exp := expected{
extendable: false,
sticky: false,
mOps: map[ACLOp]opsExpected{
ACLOpObjectHash: {
mOps: map[Op]opsExpected{
OpObjectHash: {
owner: true,
container: true,
innerRing: true,
others: true,
bearer: true,
},
ACLOpObjectRange: {
OpObjectRange: {
owner: true,
container: false,
innerRing: false,
others: true,
bearer: true,
},
ACLOpObjectSearch: {
OpObjectSearch: {
owner: true,
container: true,
innerRing: true,
others: true,
bearer: true,
},
ACLOpObjectDelete: {
OpObjectDelete: {
owner: true,
container: false,
innerRing: false,
others: false,
bearer: false,
},
ACLOpObjectPut: {
OpObjectPut: {
owner: true,
container: true,
innerRing: false,
others: false,
bearer: false,
},
ACLOpObjectHead: {
OpObjectHead: {
owner: true,
container: true,
innerRing: true,
others: true,
bearer: true,
},
ACLOpObjectGet: {
OpObjectGet: {
owner: true,
container: true,
innerRing: true,
@ -253,59 +253,59 @@ func TestBasicACLPredefined(t *testing.T) {
},
}
testBasicACLPredefined(t, BasicACLPublicRO, BasicACLNamePublicRO, exp)
testBasicPredefined(t, PublicRO, NamePublicRO, exp)
exp.extendable = true
testBasicACLPredefined(t, BasicACLPublicROExtended, BasicACLNamePublicROExtended, exp)
testBasicPredefined(t, PublicROExtended, NamePublicROExtended, exp)
})
t.Run("public-read-write", func(t *testing.T) {
exp := expected{
extendable: false,
sticky: false,
mOps: map[ACLOp]opsExpected{
ACLOpObjectHash: {
mOps: map[Op]opsExpected{
OpObjectHash: {
owner: true,
container: true,
innerRing: true,
others: true,
bearer: true,
},
ACLOpObjectRange: {
OpObjectRange: {
owner: true,
container: false,
innerRing: false,
others: true,
bearer: true,
},
ACLOpObjectSearch: {
OpObjectSearch: {
owner: true,
container: true,
innerRing: true,
others: true,
bearer: true,
},
ACLOpObjectDelete: {
OpObjectDelete: {
owner: true,
container: false,
innerRing: false,
others: true,
bearer: true,
},
ACLOpObjectPut: {
OpObjectPut: {
owner: true,
container: true,
innerRing: false,
others: true,
bearer: true,
},
ACLOpObjectHead: {
OpObjectHead: {
owner: true,
container: true,
innerRing: true,
others: true,
bearer: true,
},
ACLOpObjectGet: {
OpObjectGet: {
owner: true,
container: true,
innerRing: true,
@ -315,59 +315,59 @@ func TestBasicACLPredefined(t *testing.T) {
},
}
testBasicACLPredefined(t, BasicACLPublicRW, BasicACLNamePublicRW, exp)
testBasicPredefined(t, PublicRW, NamePublicRW, exp)
exp.extendable = true
testBasicACLPredefined(t, BasicACLPublicRWExtended, BasicACLNamePublicRWExtended, exp)
testBasicPredefined(t, PublicRWExtended, NamePublicRWExtended, exp)
})
t.Run("public-append", func(t *testing.T) {
exp := expected{
extendable: false,
sticky: false,
mOps: map[ACLOp]opsExpected{
ACLOpObjectHash: {
mOps: map[Op]opsExpected{
OpObjectHash: {
owner: true,
container: true,
innerRing: true,
others: true,
bearer: true,
},
ACLOpObjectRange: {
OpObjectRange: {
owner: true,
container: false,
innerRing: false,
others: true,
bearer: true,
},
ACLOpObjectSearch: {
OpObjectSearch: {
owner: true,
container: true,
innerRing: true,
others: true,
bearer: true,
},
ACLOpObjectDelete: {
OpObjectDelete: {
owner: true,
container: false,
innerRing: false,
others: false,
bearer: true,
},
ACLOpObjectPut: {
OpObjectPut: {
owner: true,
container: true,
innerRing: false,
others: true,
bearer: true,
},
ACLOpObjectHead: {
OpObjectHead: {
owner: true,
container: true,
innerRing: true,
others: true,
bearer: true,
},
ACLOpObjectGet: {
OpObjectGet: {
owner: true,
container: true,
innerRing: true,
@ -377,8 +377,8 @@ func TestBasicACLPredefined(t *testing.T) {
},
}
testBasicACLPredefined(t, BasicACLPublicAppend, BasicACLNamePublicAppend, exp)
testBasicPredefined(t, PublicAppend, NamePublicAppend, exp)
exp.extendable = true
testBasicACLPredefined(t, BasicACLPublicAppendExtended, BasicACLNamePublicAppendExtended, exp)
testBasicPredefined(t, PublicAppendExtended, NamePublicAppendExtended, exp)
})
}

9
container/acl/doc.go Normal file
View file

@ -0,0 +1,9 @@
/*
Package acl provides functionality to control access to data and operations on them in NeoFS containers.
Type Basic represents basic ACL of the NeoFS container which specifies the general order of data access.
Basic provides interface of rule composition. Package acl also exports some frequently used settings like
private or public.
*/
package acl

30
container/acl/init.go Normal file
View file

@ -0,0 +1,30 @@
package acl
func init() {
// left-to-right order of the object operations
orderedOps := [...]Op{
OpObjectGet,
OpObjectHead,
OpObjectPut,
OpObjectDelete,
OpObjectSearch,
OpObjectRange,
OpObjectHash,
}
mOrder = make(map[Op]uint8, len(orderedOps))
for i := range orderedOps {
mOrder[orderedOps[i]] = uint8(i)
}
// numbers are taken from NeoFS Specification
Private.FromBits(0x1C8C8CCC)
PrivateExtended.FromBits(0x0C8C8CCC)
PublicRO.FromBits(0x1FBF8CFF)
PublicROExtended.FromBits(0x0FBF8CFF)
PublicRW.FromBits(0x1FBFBFFF)
PublicRWExtended.FromBits(0x0FBFBFFF)
PublicAppend.FromBits(0x1FBF9FFF)
PublicAppendExtended.FromBits(0x0FBF9FFF)
}

View file

@ -1,4 +1,4 @@
package container
package acl
import "fmt"
@ -21,11 +21,11 @@ func isBitSet(num uint32, n uint8) bool {
return mask != 0 && num&mask == mask
}
// maps ACLOp to op-section index in BasicACL. Filled on init.
var mOrder map[ACLOp]uint8
// maps Op to op-section index in Basic. Filled on init.
var mOrder map[Op]uint8
// sets n-th bit in num for the given op. Panics if op is unsupported.
func setOpBit(num *uint32, op ACLOp, opBitPos uint8) {
func setOpBit(num *uint32, op Op, opBitPos uint8) {
n, ok := mOrder[op]
if !ok {
panic(fmt.Sprintf("op is unsupported %v", op))
@ -35,7 +35,7 @@ func setOpBit(num *uint32, op ACLOp, opBitPos uint8) {
}
// checks if n-th bit in num for the given op is set. Panics if op is unsupported.
func isOpBitSet(num uint32, op ACLOp, n uint8) bool {
func isOpBitSet(num uint32, op Op, n uint8) bool {
off, ok := mOrder[op]
if !ok {
panic(fmt.Sprintf("op is unsupported %v", op))

View file

@ -1,4 +1,4 @@
package container
package acl
import (
"fmt"
@ -30,23 +30,23 @@ func TestBits(t *testing.T) {
func TestOpBits(t *testing.T) {
num := uint32(0b_1001_0101_1100_0011_0110_0111_1000_1111)
require.Panics(t, func() { isOpBitSet(num, aclOpZero, 0) })
require.Panics(t, func() { isOpBitSet(num, aclOpLast, 0) })
require.Panics(t, func() { isOpBitSet(num, opZero, 0) })
require.Panics(t, func() { isOpBitSet(num, opLast, 0) })
cpNum := num
require.Panics(t, func() { setOpBit(&num, aclOpZero, 0) })
require.Panics(t, func() { setOpBit(&num, opZero, 0) })
require.EqualValues(t, cpNum, num)
require.Panics(t, func() { setOpBit(&num, aclOpLast, 0) })
require.Panics(t, func() { setOpBit(&num, opLast, 0) })
require.EqualValues(t, cpNum, num)
for _, tc := range []struct {
op ACLOp
op Op
set [4]bool // is bit set (left-to-right)
bits [4]uint32 // result of setting i-th bit (left-to-right) to zero num
}{
{
op: ACLOpObjectHash,
op: OpObjectHash,
set: [4]bool{false, true, false, true},
bits: [4]uint32{
0b_0000_1000_0000_0000_0000_0000_0000_0000,
@ -56,7 +56,7 @@ func TestOpBits(t *testing.T) {
},
},
{
op: ACLOpObjectRange,
op: OpObjectRange,
set: [4]bool{true, true, false, false},
bits: [4]uint32{
0b_0000_0000_1000_0000_0000_0000_0000_0000,
@ -66,7 +66,7 @@ func TestOpBits(t *testing.T) {
},
},
{
op: ACLOpObjectSearch,
op: OpObjectSearch,
set: [4]bool{false, false, true, true},
bits: [4]uint32{
0b_0000_0000_0000_1000_0000_0000_0000_0000,
@ -76,7 +76,7 @@ func TestOpBits(t *testing.T) {
},
},
{
op: ACLOpObjectDelete,
op: OpObjectDelete,
set: [4]bool{false, true, true, false},
bits: [4]uint32{
0b_0000_0000_0000_0000_1000_0000_0000_0000,
@ -86,7 +86,7 @@ func TestOpBits(t *testing.T) {
},
},
{
op: ACLOpObjectPut,
op: OpObjectPut,
set: [4]bool{false, true, true, true},
bits: [4]uint32{
0b_0000_0000_0000_0000_0000_1000_0000_0000,
@ -96,7 +96,7 @@ func TestOpBits(t *testing.T) {
},
},
{
op: ACLOpObjectHead,
op: OpObjectHead,
set: [4]bool{true, false, false, false},
bits: [4]uint32{
0b_0000_0000_0000_0000_0000_0000_1000_0000,
@ -106,7 +106,7 @@ func TestOpBits(t *testing.T) {
},
},
{
op: ACLOpObjectGet,
op: OpObjectGet,
set: [4]bool{true, true, true, true},
bits: [4]uint32{
0b_0000_0000_0000_0000_0000_0000_0000_1000,

View file

@ -1,272 +0,0 @@
package container
import (
"fmt"
"strconv"
"strings"
)
// BasicACL represents basic part of the NeoFS container's ACL. It includes
// common (pretty simple) access rules for operations inside the container.
// See NeoFS Specification for details.
//
// One can find some similarities with the traditional Unix permission, such as
// division into scopes: user, group, others
// op-permissions: read, write, etc.
// sticky bit
// However, these similarities should only be used for better understanding,
// in general these mechanisms are different.
//
// Instances can be created using built-in var declaration, but look carefully
// at the default values, and how individual permissions are regulated.
// Some frequently used values are presented in BasicACL* values.
//
// BasicACL instances are comparable: values can be compared directly using
// == operator.
type BasicACL struct {
bits uint32
}
func (x *BasicACL) fromUint32(num uint32) {
x.bits = num
}
func (x BasicACL) toUint32() uint32 {
return x.bits
}
// common bit sections.
const (
opAmount = 7
bitsPerOp = 4
bitPosFinal = opAmount * bitsPerOp
bitPosSticky = bitPosFinal + 1
)
// per-op bit order.
const (
opBitPosBearer uint8 = iota
opBitPosOthers
opBitPosContainer
opBitPosOwner
)
// DisableExtension makes BasicACL FINAL. FINAL indicates the ACL non-extendability
// in the related container.
//
// See also Extendable.
func (x *BasicACL) DisableExtension() {
setBit(&x.bits, bitPosFinal)
}
// Extendable checks if BasicACL is NOT made FINAL using DisableExtension.
//
// Zero BasicACL is NOT FINAL or extendable.
func (x BasicACL) Extendable() bool {
return !isBitSet(x.bits, bitPosFinal)
}
// MakeSticky makes BasicACL STICKY. STICKY indicates that only the owner of any
// particular object is allowed to operate on it.
//
// See also Sticky.
func (x *BasicACL) MakeSticky() {
setBit(&x.bits, bitPosSticky)
}
// Sticky checks if BasicACL is made STICKY using MakeSticky.
//
// Zero BasicACL is NOT STICKY.
func (x BasicACL) Sticky() bool {
return isBitSet(x.bits, bitPosSticky)
}
// checks if op is used by the storage nodes within replication mechanism.
func isReplicationOp(op ACLOp) bool {
//nolint:exhaustive
switch op {
case
ACLOpObjectGet,
ACLOpObjectHead,
ACLOpObjectPut,
ACLOpObjectSearch,
ACLOpObjectHash:
return true
}
return false
}
// AllowOp allows the parties with the given role to the given operation.
// Op MUST be one of the ACLOp enumeration. Role MUST be one of:
// ACLRoleOwner
// ACLRoleContainer
// ACLRoleOthers
// and if role is ACLRoleContainer, op MUST NOT be:
// ACLOpObjectGet
// ACLOpObjectHead
// ACLOpObjectPut
// ACLOpObjectSearch
// ACLOpObjectHash
//
// See also IsOpAllowed.
func (x *BasicACL) AllowOp(op ACLOp, role ACLRole) {
var bitPos uint8
switch role {
default:
panic(fmt.Sprintf("unable to set rules for unsupported role %v", role))
case ACLRoleInnerRing:
panic("basic ACL MUST NOT be modified for Inner Ring")
case ACLRoleOwner:
bitPos = opBitPosOwner
case ACLRoleContainer:
if isReplicationOp(op) {
panic("basic ACL for container replication ops MUST NOT be modified")
}
bitPos = opBitPosContainer
case ACLRoleOthers:
bitPos = opBitPosOthers
}
setOpBit(&x.bits, op, bitPos)
}
// IsOpAllowed checks if parties with the given role are allowed to the given op
// according to the BasicACL rules. Op MUST be one of the ACLOp enumeration.
// Role MUST be one of the ACLRole enumeration.
//
// Members with ACLRoleContainer role have exclusive default access to the
// operations of the data replication mechanism:
// ACLOpObjectGet
// ACLOpObjectHead
// ACLOpObjectPut
// ACLOpObjectSearch
// ACLOpObjectHash
//
// ACLRoleInnerRing members are allowed to data audit ops only:
// ACLOpObjectGet
// ACLOpObjectHead
// ACLOpObjectHash
// ACLOpObjectSearch
//
// Zero BasicACL prevents any role from accessing any operation in the absence
// of default rights.
//
// See also AllowOp.
func (x BasicACL) IsOpAllowed(op ACLOp, role ACLRole) bool {
var bitPos uint8
switch role {
default:
panic(fmt.Sprintf("role is unsupported %v", role))
case ACLRoleInnerRing:
switch op {
case
ACLOpObjectGet,
ACLOpObjectHead,
ACLOpObjectHash,
ACLOpObjectSearch:
return true
default:
return false
}
case ACLRoleOwner:
bitPos = opBitPosOwner
case ACLRoleContainer:
if isReplicationOp(op) {
return true
}
bitPos = opBitPosContainer
case ACLRoleOthers:
bitPos = opBitPosOthers
}
return isOpBitSet(x.bits, op, bitPos)
}
// AllowBearerRules allows bearer to provide extended ACL rules for the given
// operation. Bearer rules doesn't depend on container ACL
// // extensibility.
//
// See also AllowedBearerRules.
func (x *BasicACL) AllowBearerRules(op ACLOp) {
setOpBit(&x.bits, op, opBitPosBearer)
}
// AllowedBearerRules checks if bearer rules are allowed using AllowBearerRules.
// Op MUST be one of the ACLOp enumeration.
//
// Zero BasicACL disallows bearer rules for any op.
func (x BasicACL) AllowedBearerRules(op ACLOp) bool {
return isOpBitSet(x.bits, op, opBitPosBearer)
}
// EncodeToString encodes BasicACL into hexadecimal string.
//
// See also DecodeString.
func (x BasicACL) EncodeToString() string {
return strconv.FormatUint(uint64(x.bits), 16)
}
// Names of the frequently used BasicACL values.
const (
BasicACLNamePrivate = "private"
BasicACLNamePrivateExtended = "eacl-private"
BasicACLNamePublicRO = "public-read"
BasicACLNamePublicROExtended = "eacl-public-read"
BasicACLNamePublicRW = "public-read-write"
BasicACLNamePublicRWExtended = "eacl-public-read-write"
BasicACLNamePublicAppend = "public-append"
BasicACLNamePublicAppendExtended = "eacl-public-append"
)
// Frequently used BasicACL values (each value MUST NOT be modified, make a
// copy instead).
var (
BasicACLPrivate BasicACL // private
BasicACLPrivateExtended BasicACL // eacl-private
BasicACLPublicRO BasicACL // public-read
BasicACLPublicROExtended BasicACL // eacl-public-read
BasicACLPublicRW BasicACL // public-read-write
BasicACLPublicRWExtended BasicACL // eacl-public-read-write
BasicACLPublicAppend BasicACL // public-append
BasicACLPublicAppendExtended BasicACL // eacl-public-append
)
// DecodeString decodes string calculated using EncodeToString. Also supports
// human-readable names (BasicACLName* constants).
func (x *BasicACL) DecodeString(s string) error {
switch s {
case BasicACLNamePrivate:
*x = BasicACLPrivate
case BasicACLNamePrivateExtended:
*x = BasicACLPrivateExtended
case BasicACLNamePublicRO:
*x = BasicACLPublicRO
case BasicACLNamePublicROExtended:
*x = BasicACLPublicROExtended
case BasicACLNamePublicRW:
*x = BasicACLPublicRW
case BasicACLNamePublicRWExtended:
*x = BasicACLPublicRWExtended
case BasicACLNamePublicAppend:
*x = BasicACLPublicAppend
case BasicACLNamePublicAppendExtended:
*x = BasicACLPublicAppendExtended
default:
s = strings.TrimPrefix(strings.ToLower(s), "0x")
v, err := strconv.ParseUint(s, 16, 32)
if err != nil {
return fmt.Errorf("parse hex: %w", err)
}
x.bits = uint32(v)
}
return nil
}

View file

@ -7,6 +7,7 @@ import (
"github.com/nspcc-dev/neofs-api-go/v2/container"
v2netmap "github.com/nspcc-dev/neofs-api-go/v2/netmap"
"github.com/nspcc-dev/neofs-api-go/v2/refs"
"github.com/nspcc-dev/neofs-sdk-go/container/acl"
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
"github.com/nspcc-dev/neofs-sdk-go/netmap"
"github.com/nspcc-dev/neofs-sdk-go/user"
@ -22,7 +23,7 @@ type Container struct {
// Defaults:
// - token: nil;
// - sig: nil;
// - basicACL: BasicACLPrivate;
// - basicACL: Private;
// - version: version.Current;
// - nonce: random UUID;
// - attr: nil;
@ -135,13 +136,13 @@ func (c *Container) SetNonceUUID(v uuid.UUID) {
c.v2.SetNonce(data)
}
func (c *Container) BasicACL() (res BasicACL) {
res.fromUint32(c.v2.GetBasicACL())
func (c *Container) BasicACL() (res acl.Basic) {
res.FromBits(c.v2.GetBasicACL())
return
}
func (c *Container) SetBasicACL(v BasicACL) {
c.v2.SetBasicACL(v.toUint32())
func (c *Container) SetBasicACL(v acl.Basic) {
c.v2.SetBasicACL(v.Bits())
}
func (c *Container) Attributes() Attributes {

View file

@ -6,6 +6,7 @@ import (
"github.com/google/uuid"
"github.com/nspcc-dev/neofs-api-go/v2/refs"
"github.com/nspcc-dev/neofs-sdk-go/container"
"github.com/nspcc-dev/neofs-sdk-go/container/acl"
containertest "github.com/nspcc-dev/neofs-sdk-go/container/test"
netmaptest "github.com/nspcc-dev/neofs-sdk-go/netmap/test"
usertest "github.com/nspcc-dev/neofs-sdk-go/user/test"
@ -22,7 +23,7 @@ func TestNewContainer(t *testing.T) {
ownerID := usertest.ID()
policy := netmaptest.PlacementPolicy()
c.SetBasicACL(container.BasicACLPublicRW)
c.SetBasicACL(acl.PublicRW)
attrs := containertest.Attributes()
c.SetAttributes(attrs)
@ -39,7 +40,7 @@ func TestNewContainer(t *testing.T) {
require.EqualValues(t, newContainer.PlacementPolicy(), &policy)
require.EqualValues(t, newContainer.Attributes(), attrs)
require.EqualValues(t, newContainer.BasicACL(), container.BasicACLPublicRW)
require.EqualValues(t, newContainer.BasicACL(), acl.PublicRW)
newNonce, err := newContainer.NonceUUID()
require.NoError(t, err)
@ -88,7 +89,7 @@ func TestContainer_ToV2(t *testing.T) {
require.Nil(t, cnt.PlacementPolicy())
require.Nil(t, cnt.OwnerID())
require.EqualValues(t, container.BasicACLPrivate, cnt.BasicACL())
require.EqualValues(t, acl.Private, cnt.BasicACL())
require.Equal(t, version.Current(), *cnt.Version())
nonce, err := cnt.NonceUUID()

View file

@ -1,30 +0,0 @@
package container
func init() {
// left-to-right order of the object operations
orderedOps := [...]ACLOp{
ACLOpObjectGet,
ACLOpObjectHead,
ACLOpObjectPut,
ACLOpObjectDelete,
ACLOpObjectSearch,
ACLOpObjectRange,
ACLOpObjectHash,
}
mOrder = make(map[ACLOp]uint8, len(orderedOps))
for i := range orderedOps {
mOrder[orderedOps[i]] = uint8(i)
}
// numbers are taken from NeoFS Specification
BasicACLPrivate.fromUint32(0x1C8C8CCC)
BasicACLPrivateExtended.fromUint32(0x0C8C8CCC)
BasicACLPublicRO.fromUint32(0x1FBF8CFF)
BasicACLPublicROExtended.fromUint32(0x0FBF8CFF)
BasicACLPublicRW.fromUint32(0x1FBFBFFF)
BasicACLPublicRWExtended.fromUint32(0x0FBFBFFF)
BasicACLPublicAppend.fromUint32(0x1FBF9FFF)
BasicACLPublicAppendExtended.fromUint32(0x0FBF9FFF)
}

View file

@ -4,6 +4,7 @@ import (
"crypto/ecdsa"
"github.com/google/uuid"
"github.com/nspcc-dev/neofs-sdk-go/container/acl"
"github.com/nspcc-dev/neofs-sdk-go/netmap"
"github.com/nspcc-dev/neofs-sdk-go/user"
)
@ -12,7 +13,7 @@ type (
Option func(*containerOptions)
containerOptions struct {
acl BasicACL
acl acl.Basic
policy *netmap.PlacementPolicy
attributes Attributes
owner *user.ID
@ -27,24 +28,24 @@ func defaultContainerOptions() containerOptions {
}
return containerOptions{
acl: BasicACLPrivate,
acl: acl.Private,
nonce: rand,
}
}
func WithPublicBasicACL() Option {
return func(option *containerOptions) {
option.acl = BasicACLPublicRW
option.acl = acl.PublicRW
}
}
func WithReadOnlyBasicACL() Option {
return func(option *containerOptions) {
option.acl = BasicACLPublicRO
option.acl = acl.PublicRO
}
}
func WithCustomBasicACL(acl BasicACL) Option {
func WithCustomBasicACL(acl acl.Basic) Option {
return func(option *containerOptions) {
option.acl = acl
}

View file

@ -2,6 +2,7 @@ package containertest
import (
"github.com/nspcc-dev/neofs-sdk-go/container"
"github.com/nspcc-dev/neofs-sdk-go/container/acl"
cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test"
netmaptest "github.com/nspcc-dev/neofs-sdk-go/netmap/test"
usertest "github.com/nspcc-dev/neofs-sdk-go/user/test"
@ -31,7 +32,7 @@ func Container() *container.Container {
x.SetVersion(&ver)
x.SetAttributes(Attributes())
x.SetOwnerID(usertest.ID())
x.SetBasicACL(container.BasicACLPublicRW)
x.SetBasicACL(BasicACL())
p := netmaptest.PlacementPolicy()
x.SetPlacementPolicy(&p)
@ -48,3 +49,9 @@ func UsedSpaceAnnouncement() *container.UsedSpaceAnnouncement {
return x
}
// BasicACL returns random acl.Basic.
func BasicACL() (x acl.Basic) {
x.FromBits(0xffffffff)
return
}