Create ACL list and update tree package
This commit is contained in:
parent
38209fc1fb
commit
70184e1554
File diff suppressed because it is too large
Load Diff
@ -8,6 +8,12 @@ message RawChange {
|
|||||||
string id = 3;
|
string id = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message RawRecord {
|
||||||
|
bytes payload = 1;
|
||||||
|
bytes signature = 2;
|
||||||
|
string id = 3;
|
||||||
|
}
|
||||||
|
|
||||||
// the element of change tree used to store and internal apply smartBlock history
|
// the element of change tree used to store and internal apply smartBlock history
|
||||||
message ACLChange {
|
message ACLChange {
|
||||||
repeated string treeHeadIds = 1;
|
repeated string treeHeadIds = 1;
|
||||||
@ -112,7 +118,7 @@ message ACLChange {
|
|||||||
|
|
||||||
message Change {
|
message Change {
|
||||||
repeated string treeHeadIds = 1;
|
repeated string treeHeadIds = 1;
|
||||||
repeated string aclHeadIds = 2;
|
string aclHeadId = 2;
|
||||||
string snapshotBaseId = 3; // we will only have one base snapshot for both
|
string snapshotBaseId = 3; // we will only have one base snapshot for both
|
||||||
bytes changesData = 4;
|
bytes changesData = 4;
|
||||||
uint64 currentReadKeyHash = 5;
|
uint64 currentReadKeyHash = 5;
|
||||||
@ -120,3 +126,11 @@ message Change {
|
|||||||
string identity = 7;
|
string identity = 7;
|
||||||
bool isSnapshot = 8;
|
bool isSnapshot = 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message Record {
|
||||||
|
string prevId = 1;
|
||||||
|
string identity = 2;
|
||||||
|
bytes data = 3;
|
||||||
|
uint64 currentReadKeyHash = 4;
|
||||||
|
int64 timestamp = 5;
|
||||||
|
}
|
||||||
|
|||||||
@ -1,17 +1,22 @@
|
|||||||
package tree
|
package list
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/app/logger"
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||||
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys"
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/encryptionkey"
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/encryptionkey"
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/signingkey"
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/signingkey"
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/symmetric"
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/symmetric"
|
||||||
"github.com/gogo/protobuf/proto"
|
"github.com/gogo/protobuf/proto"
|
||||||
|
"go.uber.org/zap"
|
||||||
"hash/fnv"
|
"hash/fnv"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var log = logger.NewNamed("acllist")
|
||||||
|
|
||||||
var ErrNoSuchUser = errors.New("no such user")
|
var ErrNoSuchUser = errors.New("no such user")
|
||||||
var ErrFailedToDecrypt = errors.New("failed to decrypt key")
|
var ErrFailedToDecrypt = errors.New("failed to decrypt key")
|
||||||
var ErrUserRemoved = errors.New("user was removed from the document")
|
var ErrUserRemoved = errors.New("user was removed from the document")
|
||||||
@ -23,7 +28,7 @@ type ACLState struct {
|
|||||||
userReadKeys map[uint64]*symmetric.Key
|
userReadKeys map[uint64]*symmetric.Key
|
||||||
userStates map[string]*aclpb.ACLChangeUserState
|
userStates map[string]*aclpb.ACLChangeUserState
|
||||||
userInvites map[string]*aclpb.ACLChangeUserInvite
|
userInvites map[string]*aclpb.ACLChangeUserInvite
|
||||||
signingPubKeyDecoder signingkey.PubKeyDecoder
|
signingPubKeyDecoder keys.Decoder
|
||||||
encryptionKey encryptionkey.PrivKey
|
encryptionKey encryptionkey.PrivKey
|
||||||
identity string
|
identity string
|
||||||
}
|
}
|
||||||
@ -31,14 +36,14 @@ type ACLState struct {
|
|||||||
func newACLStateWithIdentity(
|
func newACLStateWithIdentity(
|
||||||
identity string,
|
identity string,
|
||||||
encryptionKey encryptionkey.PrivKey,
|
encryptionKey encryptionkey.PrivKey,
|
||||||
signingPubKeyDecoder signingkey.PubKeyDecoder) *ACLState {
|
decoder keys.Decoder) *ACLState {
|
||||||
return &ACLState{
|
return &ACLState{
|
||||||
identity: identity,
|
identity: identity,
|
||||||
encryptionKey: encryptionKey,
|
encryptionKey: encryptionKey,
|
||||||
userReadKeys: make(map[uint64]*symmetric.Key),
|
userReadKeys: make(map[uint64]*symmetric.Key),
|
||||||
userStates: make(map[string]*aclpb.ACLChangeUserState),
|
userStates: make(map[string]*aclpb.ACLChangeUserState),
|
||||||
userInvites: make(map[string]*aclpb.ACLChangeUserInvite),
|
userInvites: make(map[string]*aclpb.ACLChangeUserInvite),
|
||||||
signingPubKeyDecoder: signingPubKeyDecoder,
|
signingPubKeyDecoder: decoder,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,10 +55,18 @@ func newACLState() *ACLState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *ACLState) applyChange(change *aclpb.Change) (err error) {
|
func (st *ACLState) CurrentReadKeyHash() uint64 {
|
||||||
|
return st.currentReadKeyHash
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *ACLState) UserReadKeys() map[uint64]*symmetric.Key {
|
||||||
|
return st.userReadKeys
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *ACLState) applyRecord(record *aclpb.Record) (err error) {
|
||||||
aclData := &aclpb.ACLChangeACLData{}
|
aclData := &aclpb.ACLChangeACLData{}
|
||||||
|
|
||||||
err = proto.Unmarshal(change.ChangesData, aclData)
|
err = proto.Unmarshal(record.Data, aclData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -62,27 +75,27 @@ func (st *ACLState) applyChange(change *aclpb.Change) (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
st.currentReadKeyHash = change.CurrentReadKeyHash
|
st.currentReadKeyHash = record.CurrentReadKeyHash
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return st.applyChangeData(aclData, change.CurrentReadKeyHash, change.Identity)
|
return st.applyChangeData(aclData, record.CurrentReadKeyHash, record.Identity)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *ACLState) applyChangeAndUpdate(changeWrapper *Change) (err error) {
|
func (st *ACLState) applyChangeAndUpdate(recordWrapper *Record) (err error) {
|
||||||
change := changeWrapper.Content
|
change := recordWrapper.Content
|
||||||
aclData := &aclpb.ACLChangeACLData{}
|
aclData := &aclpb.ACLChangeACLData{}
|
||||||
|
|
||||||
if changeWrapper.ParsedModel != nil {
|
if recordWrapper.ParsedModel != nil {
|
||||||
aclData = changeWrapper.ParsedModel.(*aclpb.ACLChangeACLData)
|
aclData = recordWrapper.ParsedModel.(*aclpb.ACLChangeACLData)
|
||||||
} else {
|
} else {
|
||||||
err = proto.Unmarshal(change.ChangesData, aclData)
|
err = proto.Unmarshal(change.Data, aclData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
changeWrapper.ParsedModel = aclData
|
recordWrapper.ParsedModel = aclData
|
||||||
}
|
}
|
||||||
|
|
||||||
return st.applyChangeData(aclData, changeWrapper.Content.CurrentReadKeyHash, changeWrapper.Content.Identity)
|
return st.applyChangeData(aclData, recordWrapper.Content.CurrentReadKeyHash, recordWrapper.Content.Identity)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *ACLState) applyChangeData(changeData *aclpb.ACLChangeACLData, hash uint64, identity string) (err error) {
|
func (st *ACLState) applyChangeData(changeData *aclpb.ACLChangeACLData, hash uint64, identity string) (err error) {
|
||||||
@ -111,7 +124,7 @@ func (st *ACLState) applyChangeData(changeData *aclpb.ACLChangeACLData, hash uin
|
|||||||
|
|
||||||
for _, ch := range changeData.GetAclContent() {
|
for _, ch := range changeData.GetAclContent() {
|
||||||
if err = st.applyChangeContent(ch); err != nil {
|
if err = st.applyChangeContent(ch); err != nil {
|
||||||
log.Infof("error while applying changes: %v; ignore", err)
|
log.Info("error while applying changes: %v; ignore", zap.Error(err))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -174,7 +187,7 @@ func (st *ACLState) applyUserJoin(ch *aclpb.ACLChangeUserJoin) error {
|
|||||||
return fmt.Errorf("failed to decode signing identity as bytes")
|
return fmt.Errorf("failed to decode signing identity as bytes")
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := verificationKey.Verify(rawSignedId, signature)
|
res, err := verificationKey.(signingkey.PubKey).Verify(rawSignedId, signature)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("verification returned error: %w", err)
|
return fmt.Errorf("verification returned error: %w", err)
|
||||||
}
|
}
|
||||||
51
pkg/acl/list/aclstatebuilder.go
Normal file
51
pkg/acl/list/aclstatebuilder.go
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
package list
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/account"
|
||||||
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys"
|
||||||
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/encryptionkey"
|
||||||
|
)
|
||||||
|
|
||||||
|
type aclStateBuilder struct {
|
||||||
|
log ACLList
|
||||||
|
identity string
|
||||||
|
key encryptionkey.PrivKey
|
||||||
|
decoder keys.Decoder
|
||||||
|
}
|
||||||
|
|
||||||
|
func newACLStateBuilderWithIdentity(decoder keys.Decoder, accountData *account.AccountData) *aclStateBuilder {
|
||||||
|
return &aclStateBuilder{
|
||||||
|
decoder: decoder,
|
||||||
|
identity: accountData.Identity,
|
||||||
|
key: accountData.EncKey,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newACLStateBuilder() *aclStateBuilder {
|
||||||
|
return &aclStateBuilder{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sb *aclStateBuilder) Init(aclLog ACLList) error {
|
||||||
|
sb.log = aclLog
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sb *aclStateBuilder) Build() (*ACLState, error) {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
state *ACLState
|
||||||
|
)
|
||||||
|
|
||||||
|
if sb.decoder != nil {
|
||||||
|
state = newACLStateWithIdentity(sb.identity, sb.key, sb.decoder)
|
||||||
|
} else {
|
||||||
|
state = newACLState()
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.log.Iterate(func(c *Record) (isContinue bool) {
|
||||||
|
err = state.applyChangeAndUpdate(c)
|
||||||
|
return err == nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return state, err
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package tree
|
package list
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/account"
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/account"
|
||||||
@ -20,7 +20,7 @@ type ACLChangeBuilder interface {
|
|||||||
|
|
||||||
type aclChangeBuilder struct {
|
type aclChangeBuilder struct {
|
||||||
aclState *ACLState
|
aclState *ACLState
|
||||||
tree *Tree
|
list ACLList
|
||||||
acc *account.AccountData
|
acc *account.AccountData
|
||||||
|
|
||||||
aclData *aclpb.ACLChangeACLData
|
aclData *aclpb.ACLChangeACLData
|
||||||
@ -33,9 +33,9 @@ func newACLChangeBuilder() *aclChangeBuilder {
|
|||||||
return &aclChangeBuilder{}
|
return &aclChangeBuilder{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *aclChangeBuilder) Init(state *ACLState, tree *Tree, acc *account.AccountData) {
|
func (c *aclChangeBuilder) Init(state *ACLState, list ACLList, acc *account.AccountData) {
|
||||||
c.aclState = state
|
c.aclState = state
|
||||||
c.tree = tree
|
c.list = list
|
||||||
c.acc = acc
|
c.acc = acc
|
||||||
|
|
||||||
c.aclData = &aclpb.ACLChangeACLData{}
|
c.aclData = &aclpb.ACLChangeACLData{}
|
||||||
@ -93,30 +93,25 @@ func (c *aclChangeBuilder) UserAdd(identity string, encryptionKey encryptionkey.
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *aclChangeBuilder) BuildAndApply() (*Change, []byte, error) {
|
func (c *aclChangeBuilder) BuildAndApply() (*Record, []byte, error) {
|
||||||
aclChange := &aclpb.Change{
|
aclRecord := &aclpb.Record{
|
||||||
TreeHeadIds: c.tree.Heads(),
|
PrevId: c.list.Last().Id,
|
||||||
SnapshotBaseId: c.tree.RootId(),
|
|
||||||
CurrentReadKeyHash: c.readKeyHash,
|
CurrentReadKeyHash: c.readKeyHash,
|
||||||
Timestamp: int64(time.Now().Nanosecond()),
|
Timestamp: int64(time.Now().Nanosecond()),
|
||||||
Identity: c.acc.Identity,
|
Identity: c.acc.Identity,
|
||||||
}
|
}
|
||||||
if c.aclState.currentReadKeyHash == 0 {
|
|
||||||
// setting IsSnapshot for initial change
|
|
||||||
aclChange.IsSnapshot = true
|
|
||||||
}
|
|
||||||
|
|
||||||
marshalledData, err := proto.Marshal(c.aclData)
|
marshalledData, err := proto.Marshal(c.aclData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
aclChange.ChangesData = marshalledData
|
aclRecord.Data = marshalledData
|
||||||
err = c.aclState.applyChange(aclChange)
|
err = c.aclState.applyRecord(aclRecord)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
fullMarshalledChange, err := proto.Marshal(aclChange)
|
fullMarshalledChange, err := proto.Marshal(aclRecord)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@ -128,7 +123,7 @@ func (c *aclChangeBuilder) BuildAndApply() (*Change, []byte, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
ch := NewChange(id, aclChange)
|
ch := NewRecord(id, aclRecord)
|
||||||
ch.ParsedModel = c.aclData
|
ch.ParsedModel = c.aclData
|
||||||
ch.Sign = signature
|
ch.Sign = signature
|
||||||
|
|
||||||
16
pkg/acl/list/list.go
Normal file
16
pkg/acl/list/list.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package list
|
||||||
|
|
||||||
|
import "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/tree"
|
||||||
|
|
||||||
|
type IterFunc = func(record *Record) (IsContinue bool)
|
||||||
|
|
||||||
|
type ACLList interface {
|
||||||
|
tree.RWLocker
|
||||||
|
ID() string
|
||||||
|
ACLState() ACLState
|
||||||
|
IsAfter(first string, second string) (bool, error)
|
||||||
|
Last() *Record
|
||||||
|
Get(id string) (*Record, error)
|
||||||
|
Iterate(iterFunc IterFunc)
|
||||||
|
IterateFrom(startId string, iterFunc IterFunc)
|
||||||
|
}
|
||||||
17
pkg/acl/list/record.go
Normal file
17
pkg/acl/list/record.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package list
|
||||||
|
|
||||||
|
import "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||||
|
|
||||||
|
type Record struct {
|
||||||
|
Id string
|
||||||
|
Content *aclpb.Record
|
||||||
|
ParsedModel interface{}
|
||||||
|
Sign []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRecord(id string, aclRecord *aclpb.Record) *Record {
|
||||||
|
return &Record{
|
||||||
|
Id: id,
|
||||||
|
Content: aclRecord,
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,102 +0,0 @@
|
|||||||
package tree
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/account"
|
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/encryptionkey"
|
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/signingkey"
|
|
||||||
)
|
|
||||||
|
|
||||||
type aclStateBuilder struct {
|
|
||||||
tree *Tree
|
|
||||||
identity string
|
|
||||||
key encryptionkey.PrivKey
|
|
||||||
decoder signingkey.PubKeyDecoder
|
|
||||||
}
|
|
||||||
|
|
||||||
func newACLStateBuilderWithIdentity(decoder signingkey.PubKeyDecoder, accountData *account.AccountData) *aclStateBuilder {
|
|
||||||
return &aclStateBuilder{
|
|
||||||
decoder: decoder,
|
|
||||||
identity: accountData.Identity,
|
|
||||||
key: accountData.EncKey,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newACLStateBuilder() *aclStateBuilder {
|
|
||||||
return &aclStateBuilder{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sb *aclStateBuilder) Init(tree *Tree) error {
|
|
||||||
sb.tree = tree
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sb *aclStateBuilder) Build() (*ACLState, error) {
|
|
||||||
state, _, err := sb.BuildBefore("")
|
|
||||||
return state, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sb *aclStateBuilder) BuildBefore(beforeId string) (*ACLState, bool, error) {
|
|
||||||
var (
|
|
||||||
err error
|
|
||||||
startChange = sb.tree.root
|
|
||||||
state *ACLState
|
|
||||||
foundId = false
|
|
||||||
)
|
|
||||||
|
|
||||||
if sb.decoder != nil {
|
|
||||||
state = newACLStateWithIdentity(sb.identity, sb.key, sb.decoder)
|
|
||||||
} else {
|
|
||||||
state = newACLState()
|
|
||||||
}
|
|
||||||
|
|
||||||
if beforeId == startChange.Id {
|
|
||||||
return state, true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
iterFunc := func(c *Change) (isContinue bool) {
|
|
||||||
defer func() {
|
|
||||||
if err == nil {
|
|
||||||
startChange = c
|
|
||||||
} else if err != ErrDocumentForbidden {
|
|
||||||
log.Errorf("marking change %s as invalid: %v", c.Id, err)
|
|
||||||
sb.tree.RemoveInvalidChange(c.Id)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
err = state.applyChangeAndUpdate(c)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// the user can't make changes
|
|
||||||
if !state.hasPermission(c.Content.Identity, aclpb.ACLChange_Writer) && !state.hasPermission(c.Content.Identity, aclpb.ACLChange_Admin) {
|
|
||||||
err = fmt.Errorf("user %s cannot make changes", c.Content.Identity)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Id == beforeId {
|
|
||||||
foundId = true
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
sb.tree.IterateSkip(sb.tree.root.Id, startChange.Id, iterFunc)
|
|
||||||
if err == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// the user is forbidden to access the document
|
|
||||||
if err == ErrDocumentForbidden {
|
|
||||||
return nil, foundId, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// otherwise we have to continue from the change which we had
|
|
||||||
err = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return state, foundId, err
|
|
||||||
}
|
|
||||||
@ -1,525 +0,0 @@
|
|||||||
package tree
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/account"
|
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treestorage"
|
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treestorage/treepb"
|
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/signingkey"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
type AddResultSummary int
|
|
||||||
|
|
||||||
var ErrTreeWithoutIdentity = errors.New("acl tree is created without identity")
|
|
||||||
var ErrHasInvalidChanges = errors.New("the change is invalid")
|
|
||||||
|
|
||||||
const (
|
|
||||||
AddResultSummaryNothing AddResultSummary = iota
|
|
||||||
AddResultSummaryAppend
|
|
||||||
AddResultSummaryRebuild
|
|
||||||
)
|
|
||||||
|
|
||||||
type AddResult struct {
|
|
||||||
OldHeads []string
|
|
||||||
Heads []string
|
|
||||||
Added []*aclpb.RawChange
|
|
||||||
// TODO: add summary for changes
|
|
||||||
Summary AddResultSummary
|
|
||||||
}
|
|
||||||
|
|
||||||
type ACLTreeUpdateListener interface {
|
|
||||||
Update(tree ACLTree)
|
|
||||||
Rebuild(tree ACLTree)
|
|
||||||
}
|
|
||||||
|
|
||||||
type NoOpListener struct{}
|
|
||||||
|
|
||||||
func (n NoOpListener) Update(tree ACLTree) {}
|
|
||||||
|
|
||||||
func (n NoOpListener) Rebuild(tree ACLTree) {}
|
|
||||||
|
|
||||||
type RWLocker interface {
|
|
||||||
sync.Locker
|
|
||||||
RLock()
|
|
||||||
RUnlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
var ErrNoCommonSnapshot = errors.New("trees doesn't have a common snapshot")
|
|
||||||
|
|
||||||
type ACLTree interface {
|
|
||||||
RWLocker
|
|
||||||
CommonTree
|
|
||||||
|
|
||||||
ACLState() *ACLState
|
|
||||||
AddContent(ctx context.Context, f func(builder ACLChangeBuilder) error) (*aclpb.RawChange, error)
|
|
||||||
AddRawChanges(ctx context.Context, changes ...*aclpb.RawChange) (AddResult, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type aclTree struct {
|
|
||||||
treeStorage treestorage.TreeStorage
|
|
||||||
accountData *account.AccountData
|
|
||||||
updateListener ACLTreeUpdateListener
|
|
||||||
|
|
||||||
id string
|
|
||||||
header *treepb.TreeHeader
|
|
||||||
tree *Tree
|
|
||||||
aclState *ACLState
|
|
||||||
|
|
||||||
treeBuilder *treeBuilder
|
|
||||||
aclStateBuilder *aclStateBuilder
|
|
||||||
changeBuilder *aclChangeBuilder
|
|
||||||
|
|
||||||
sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func BuildACLTreeWithIdentity(t treestorage.TreeStorage, acc *account.AccountData, listener ACLTreeUpdateListener) (ACLTree, error) {
|
|
||||||
treeBuilder := newTreeBuilder(t, acc.Decoder)
|
|
||||||
aclStateBuilder := newACLStateBuilderWithIdentity(acc.Decoder, acc)
|
|
||||||
changeBuilder := newACLChangeBuilder()
|
|
||||||
|
|
||||||
aclTree := &aclTree{
|
|
||||||
treeStorage: t,
|
|
||||||
accountData: acc,
|
|
||||||
tree: nil,
|
|
||||||
aclState: nil,
|
|
||||||
treeBuilder: treeBuilder,
|
|
||||||
aclStateBuilder: aclStateBuilder,
|
|
||||||
changeBuilder: changeBuilder,
|
|
||||||
updateListener: listener,
|
|
||||||
}
|
|
||||||
err := aclTree.rebuildFromStorage()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = aclTree.removeOrphans()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = t.SetHeads(aclTree.Heads())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
aclTree.id, err = t.TreeID()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
aclTree.header, err = t.Header()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if listener != nil {
|
|
||||||
listener.Rebuild(aclTree)
|
|
||||||
}
|
|
||||||
|
|
||||||
return aclTree, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func BuildACLTree(t treestorage.TreeStorage, decoder signingkey.PubKeyDecoder, listener ACLTreeUpdateListener) (ACLTree, error) {
|
|
||||||
treeBuilder := newTreeBuilder(t, decoder)
|
|
||||||
aclStateBuilder := newACLStateBuilder()
|
|
||||||
changeBuilder := newACLChangeBuilder()
|
|
||||||
|
|
||||||
aclTree := &aclTree{
|
|
||||||
treeStorage: t,
|
|
||||||
tree: nil,
|
|
||||||
aclState: nil,
|
|
||||||
treeBuilder: treeBuilder,
|
|
||||||
aclStateBuilder: aclStateBuilder,
|
|
||||||
changeBuilder: changeBuilder,
|
|
||||||
updateListener: listener,
|
|
||||||
}
|
|
||||||
err := aclTree.rebuildFromStorage()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = aclTree.removeOrphans()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = t.SetHeads(aclTree.Heads())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
aclTree.id, err = t.TreeID()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
aclTree.header, err = t.Header()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if listener != nil {
|
|
||||||
listener.Rebuild(aclTree)
|
|
||||||
}
|
|
||||||
|
|
||||||
return aclTree, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *aclTree) removeOrphans() error {
|
|
||||||
// removing attached or invalid orphans
|
|
||||||
var toRemove []string
|
|
||||||
|
|
||||||
orphans, err := a.treeStorage.Orphans()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, orphan := range orphans {
|
|
||||||
if _, exists := a.tree.attached[orphan]; exists {
|
|
||||||
toRemove = append(toRemove, orphan)
|
|
||||||
}
|
|
||||||
if _, exists := a.tree.invalidChanges[orphan]; exists {
|
|
||||||
toRemove = append(toRemove, orphan)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return a.treeStorage.RemoveOrphans(toRemove...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *aclTree) rebuildFromStorage() (err error) {
|
|
||||||
a.treeBuilder.Init()
|
|
||||||
|
|
||||||
a.tree, err = a.treeBuilder.Build(true)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = a.aclStateBuilder.Init(a.tree)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
a.aclState, err = a.aclStateBuilder.Build()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *aclTree) ID() string {
|
|
||||||
return a.id
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *aclTree) Header() *treepb.TreeHeader {
|
|
||||||
return a.header
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *aclTree) ACLState() *ACLState {
|
|
||||||
return a.aclState
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *aclTree) Storage() treestorage.TreeStorage {
|
|
||||||
return a.treeStorage
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *aclTree) AddContent(ctx context.Context, build func(builder ACLChangeBuilder) error) (*aclpb.RawChange, error) {
|
|
||||||
if a.accountData == nil {
|
|
||||||
return nil, ErrTreeWithoutIdentity
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
// TODO: should this be called in a separate goroutine to prevent accidental cycles (tree->updater->tree)
|
|
||||||
if a.updateListener != nil {
|
|
||||||
a.updateListener.Update(a)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
a.changeBuilder.Init(a.aclState, a.tree, a.accountData)
|
|
||||||
err := build(a.changeBuilder)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ch, marshalled, err := a.changeBuilder.BuildAndApply()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
a.tree.AddFast(ch)
|
|
||||||
rawCh := &aclpb.RawChange{
|
|
||||||
Payload: marshalled,
|
|
||||||
Signature: ch.Signature(),
|
|
||||||
Id: ch.Id,
|
|
||||||
}
|
|
||||||
|
|
||||||
err = a.treeStorage.AddRawChange(rawCh)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = a.treeStorage.SetHeads([]string{ch.Id})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return rawCh, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *aclTree) AddRawChanges(ctx context.Context, rawChanges ...*aclpb.RawChange) (AddResult, error) {
|
|
||||||
// TODO: make proper error handling, because there are a lot of corner cases where this will break
|
|
||||||
var err error
|
|
||||||
var mode Mode
|
|
||||||
|
|
||||||
var changes []*Change // TODO: = addChangesBuf[:0] ...
|
|
||||||
var notSeenIdx []int
|
|
||||||
prevHeads := a.tree.Heads()
|
|
||||||
for idx, ch := range rawChanges {
|
|
||||||
if a.HasChange(ch.Id) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
change, err := NewFromRawChange(ch)
|
|
||||||
// TODO: think what if we will have incorrect signatures on rawChanges, how everything will work
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
changes = append(changes, change)
|
|
||||||
notSeenIdx = append(notSeenIdx, idx)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(notSeenIdx) == 0 {
|
|
||||||
return AddResult{
|
|
||||||
OldHeads: prevHeads,
|
|
||||||
Heads: prevHeads,
|
|
||||||
Summary: AddResultSummaryNothing,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = a.removeOrphans()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = a.treeStorage.SetHeads(a.tree.Heads())
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if a.updateListener == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch mode {
|
|
||||||
case Append:
|
|
||||||
a.updateListener.Update(a)
|
|
||||||
case Rebuild:
|
|
||||||
a.updateListener.Rebuild(a)
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
getAddedChanges := func() []*aclpb.RawChange {
|
|
||||||
var added []*aclpb.RawChange
|
|
||||||
for _, idx := range notSeenIdx {
|
|
||||||
rawChange := rawChanges[idx]
|
|
||||||
if _, exists := a.tree.attached[rawChange.Id]; exists {
|
|
||||||
added = append(added, rawChange)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return added
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, ch := range changes {
|
|
||||||
err = a.treeStorage.AddChange(ch)
|
|
||||||
if err != nil {
|
|
||||||
return AddResult{}, err
|
|
||||||
}
|
|
||||||
err = a.treeStorage.AddOrphans(ch.Id)
|
|
||||||
if err != nil {
|
|
||||||
return AddResult{}, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rebuild := func() (AddResult, error) {
|
|
||||||
err = a.rebuildFromStorage()
|
|
||||||
if err != nil {
|
|
||||||
return AddResult{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return AddResult{
|
|
||||||
OldHeads: prevHeads,
|
|
||||||
Heads: a.tree.Heads(),
|
|
||||||
Added: getAddedChanges(),
|
|
||||||
Summary: AddResultSummaryRebuild,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
mode = a.tree.Add(changes...)
|
|
||||||
switch mode {
|
|
||||||
case Nothing:
|
|
||||||
for _, ch := range changes {
|
|
||||||
// rebuilding if the snapshot is different from the root
|
|
||||||
if ch.SnapshotId != a.tree.RootId() && ch.SnapshotId != "" {
|
|
||||||
return rebuild()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return AddResult{
|
|
||||||
OldHeads: prevHeads,
|
|
||||||
Heads: prevHeads,
|
|
||||||
Summary: AddResultSummaryNothing,
|
|
||||||
}, nil
|
|
||||||
|
|
||||||
case Rebuild:
|
|
||||||
return rebuild()
|
|
||||||
default:
|
|
||||||
// just rebuilding the state from start without reloading everything from tree storage
|
|
||||||
// as an optimization we could've started from current heads, but I didn't implement that
|
|
||||||
a.aclState, err = a.aclStateBuilder.Build()
|
|
||||||
if err != nil {
|
|
||||||
return AddResult{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return AddResult{
|
|
||||||
OldHeads: prevHeads,
|
|
||||||
Heads: a.tree.Heads(),
|
|
||||||
Added: getAddedChanges(),
|
|
||||||
Summary: AddResultSummaryAppend,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *aclTree) Iterate(f func(change *Change) bool) {
|
|
||||||
a.tree.Iterate(a.tree.RootId(), f)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *aclTree) IterateFrom(s string, f func(change *Change) bool) {
|
|
||||||
a.tree.Iterate(s, f)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *aclTree) HasChange(s string) bool {
|
|
||||||
_, attachedExists := a.tree.attached[s]
|
|
||||||
_, unattachedExists := a.tree.unAttached[s]
|
|
||||||
_, invalidExists := a.tree.invalidChanges[s]
|
|
||||||
return attachedExists || unattachedExists || invalidExists
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *aclTree) Heads() []string {
|
|
||||||
return a.tree.Heads()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *aclTree) Root() *Change {
|
|
||||||
return a.tree.Root()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *aclTree) Close() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *aclTree) SnapshotPath() []string {
|
|
||||||
// TODO: think about caching this
|
|
||||||
|
|
||||||
var path []string
|
|
||||||
// TODO: think that the user may have not all of the snapshots locally
|
|
||||||
currentSnapshotId := a.tree.RootId()
|
|
||||||
for currentSnapshotId != "" {
|
|
||||||
sn, err := a.treeBuilder.loadChange(currentSnapshotId)
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
path = append(path, currentSnapshotId)
|
|
||||||
currentSnapshotId = sn.SnapshotId
|
|
||||||
}
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *aclTree) ChangesAfterCommonSnapshot(theirPath []string) ([]*aclpb.RawChange, error) {
|
|
||||||
// TODO: think about when the clients will have their full acl tree and thus full snapshots
|
|
||||||
// but no changes after some of the snapshots
|
|
||||||
|
|
||||||
var (
|
|
||||||
isNewDocument = len(theirPath) == 0
|
|
||||||
ourPath = a.SnapshotPath()
|
|
||||||
// by default returning everything we have
|
|
||||||
commonSnapshot = ourPath[len(ourPath)-1] // TODO: root snapshot, probably it is better to have a specific method in treestorage
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
// if this is non-empty request
|
|
||||||
if !isNewDocument {
|
|
||||||
commonSnapshot, err = commonSnapshotForTwoPaths(ourPath, theirPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var rawChanges []*aclpb.RawChange
|
|
||||||
// using custom load function to skip verification step and save raw changes
|
|
||||||
load := func(id string) (*Change, error) {
|
|
||||||
raw, err := a.treeStorage.GetChange(context.Background(), id)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ch, err := NewFromRawChange(raw)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
rawChanges = append(rawChanges, raw)
|
|
||||||
return ch, nil
|
|
||||||
}
|
|
||||||
// we presume that we have everything after the common snapshot, though this may not be the case in case of clients and only ACL tree changes
|
|
||||||
log.With(
|
|
||||||
zap.Strings("heads", a.tree.Heads()),
|
|
||||||
zap.String("breakpoint", commonSnapshot),
|
|
||||||
zap.String("id", a.id)).
|
|
||||||
Debug("getting all changes from common snapshot")
|
|
||||||
_, err = a.treeBuilder.dfs(a.tree.Heads(), commonSnapshot, load)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if isNewDocument {
|
|
||||||
// adding snapshot to raw changes
|
|
||||||
_, err = load(commonSnapshot)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.With(
|
|
||||||
zap.Int("len(changes)", len(rawChanges)),
|
|
||||||
zap.String("id", a.id)).
|
|
||||||
Debug("returning all changes after common snapshot")
|
|
||||||
|
|
||||||
return rawChanges, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *aclTree) DebugDump() (string, error) {
|
|
||||||
return a.tree.Graph(ACLDescriptionParser)
|
|
||||||
}
|
|
||||||
|
|
||||||
func commonSnapshotForTwoPaths(ourPath []string, theirPath []string) (string, error) {
|
|
||||||
var i int
|
|
||||||
var j int
|
|
||||||
log.With(zap.Strings("our path", ourPath), zap.Strings("their path", theirPath)).
|
|
||||||
Debug("finding common snapshot for two paths")
|
|
||||||
OuterLoop:
|
|
||||||
// find starting point from the right
|
|
||||||
for i = len(ourPath) - 1; i >= 0; i-- {
|
|
||||||
for j = len(theirPath) - 1; j >= 0; j-- {
|
|
||||||
// most likely there would be only one comparison, because mostly the snapshot path will start from the root for nodes
|
|
||||||
if ourPath[i] == theirPath[j] {
|
|
||||||
break OuterLoop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if i < 0 || j < 0 {
|
|
||||||
return "", ErrNoCommonSnapshot
|
|
||||||
}
|
|
||||||
// find last common element of the sequence moving from right to left
|
|
||||||
for i >= 0 && j >= 0 {
|
|
||||||
if ourPath[i] == theirPath[j] {
|
|
||||||
i--
|
|
||||||
j--
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ourPath[i+1], nil
|
|
||||||
}
|
|
||||||
@ -1,7 +1,9 @@
|
|||||||
package tree
|
package tree
|
||||||
|
|
||||||
|
import "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/list"
|
||||||
|
|
||||||
type DocTreeValidator interface {
|
type DocTreeValidator interface {
|
||||||
ValidateTree(tree *Tree, aclTree ACLTree) error
|
ValidateTree(tree *Tree, aclList list.ACLList) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type docTreeValidator struct{}
|
type docTreeValidator struct{}
|
||||||
@ -9,7 +11,7 @@ type docTreeValidator struct{}
|
|||||||
func newTreeValidator() DocTreeValidator {
|
func newTreeValidator() DocTreeValidator {
|
||||||
return &docTreeValidator{}
|
return &docTreeValidator{}
|
||||||
}
|
}
|
||||||
func (v *docTreeValidator) ValidateTree(tree *Tree, aclTree ACLTree) error {
|
func (v *docTreeValidator) ValidateTree(tree *Tree, list list.ACLList) error {
|
||||||
// TODO: add validation logic where we check that the change refers to correct acl heads
|
// TODO: add validation logic where we check that the change refers to correct acl heads
|
||||||
// that means that more recent changes should refer to more recent acl heads
|
// that means that more recent changes should refer to more recent acl heads
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@ -1,13 +1,5 @@
|
|||||||
package tree
|
package tree
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
|
||||||
"github.com/gogo/protobuf/proto"
|
|
||||||
"strings"
|
|
||||||
"unicode"
|
|
||||||
)
|
|
||||||
|
|
||||||
type DescriptionParser interface {
|
type DescriptionParser interface {
|
||||||
ParseChange(*Change) ([]string, error)
|
ParseChange(*Change) ([]string, error)
|
||||||
}
|
}
|
||||||
@ -19,35 +11,3 @@ type noopDescriptionParser struct{}
|
|||||||
func (n noopDescriptionParser) ParseChange(change *Change) ([]string, error) {
|
func (n noopDescriptionParser) ParseChange(change *Change) ([]string, error) {
|
||||||
return []string{"DOC"}, nil
|
return []string{"DOC"}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var ACLDescriptionParser = aclDescriptionParser{}
|
|
||||||
|
|
||||||
type aclDescriptionParser struct{}
|
|
||||||
|
|
||||||
func (a aclDescriptionParser) ParseChange(changeWrapper *Change) (res []string, err error) {
|
|
||||||
change := changeWrapper.Content
|
|
||||||
aclData := &aclpb.ACLChangeACLData{}
|
|
||||||
|
|
||||||
if changeWrapper.ParsedModel != nil {
|
|
||||||
aclData = changeWrapper.ParsedModel.(*aclpb.ACLChangeACLData)
|
|
||||||
} else {
|
|
||||||
err = proto.Unmarshal(change.ChangesData, aclData)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var chSymbs []string
|
|
||||||
for _, chc := range aclData.AclContent {
|
|
||||||
tp := fmt.Sprintf("%T", chc.Value)
|
|
||||||
tp = strings.Replace(tp, "ACLChangeACLContentValueValueOf", "", 1)
|
|
||||||
res := ""
|
|
||||||
for _, ts := range tp {
|
|
||||||
if unicode.IsUpper(ts) {
|
|
||||||
res += string(ts)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
chSymbs = append(chSymbs, res)
|
|
||||||
}
|
|
||||||
return chSymbs, nil
|
|
||||||
}
|
|
||||||
|
|||||||
@ -2,8 +2,10 @@ package tree
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/account"
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/account"
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||||
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/list"
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treestorage"
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treestorage"
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treestorage/treepb"
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treestorage/treepb"
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/cid"
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/cid"
|
||||||
@ -20,11 +22,37 @@ type TreeUpdateListener interface {
|
|||||||
Rebuild(tree DocTree)
|
Rebuild(tree DocTree)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RWLocker interface {
|
||||||
|
sync.Locker
|
||||||
|
RLock()
|
||||||
|
RUnlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
var ErrHasInvalidChanges = errors.New("the change is invalid")
|
||||||
|
var ErrNoCommonSnapshot = errors.New("trees doesn't have a common snapshot")
|
||||||
|
var ErrTreeWithoutIdentity = errors.New("acl tree is created without identity")
|
||||||
|
|
||||||
|
type AddResultSummary int
|
||||||
|
|
||||||
|
const (
|
||||||
|
AddResultSummaryNothing AddResultSummary = iota
|
||||||
|
AddResultSummaryAppend
|
||||||
|
AddResultSummaryRebuild
|
||||||
|
)
|
||||||
|
|
||||||
|
type AddResult struct {
|
||||||
|
OldHeads []string
|
||||||
|
Heads []string
|
||||||
|
Added []*aclpb.RawChange
|
||||||
|
// TODO: add summary for changes
|
||||||
|
Summary AddResultSummary
|
||||||
|
}
|
||||||
|
|
||||||
type DocTree interface {
|
type DocTree interface {
|
||||||
RWLocker
|
RWLocker
|
||||||
CommonTree
|
CommonTree
|
||||||
AddContent(ctx context.Context, aclTree ACLTree, content proto.Marshaler, isSnapshot bool) (*aclpb.RawChange, error)
|
AddContent(ctx context.Context, aclList list.ACLList, content proto.Marshaler, isSnapshot bool) (*aclpb.RawChange, error)
|
||||||
AddRawChanges(ctx context.Context, aclTree ACLTree, changes ...*aclpb.RawChange) (AddResult, error)
|
AddRawChanges(ctx context.Context, aclList list.ACLList, changes ...*aclpb.RawChange) (AddResult, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type docTree struct {
|
type docTree struct {
|
||||||
@ -48,7 +76,7 @@ type docTree struct {
|
|||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func BuildDocTreeWithIdentity(t treestorage.TreeStorage, acc *account.AccountData, listener TreeUpdateListener, aclTree ACLTree) (DocTree, error) {
|
func BuildDocTreeWithIdentity(t treestorage.TreeStorage, acc *account.AccountData, listener TreeUpdateListener, aclList list.ACLList) (DocTree, error) {
|
||||||
treeBuilder := newTreeBuilder(t, acc.Decoder)
|
treeBuilder := newTreeBuilder(t, acc.Decoder)
|
||||||
validator := newTreeValidator()
|
validator := newTreeValidator()
|
||||||
|
|
||||||
@ -64,7 +92,7 @@ func BuildDocTreeWithIdentity(t treestorage.TreeStorage, acc *account.AccountDat
|
|||||||
notSeenIdxBuf: make([]int, 0, 10),
|
notSeenIdxBuf: make([]int, 0, 10),
|
||||||
identityKeys: make(map[string]signingkey.PubKey),
|
identityKeys: make(map[string]signingkey.PubKey),
|
||||||
}
|
}
|
||||||
err := docTree.rebuildFromStorage(aclTree, nil)
|
err := docTree.rebuildFromStorage(aclList, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -84,7 +112,7 @@ func BuildDocTreeWithIdentity(t treestorage.TreeStorage, acc *account.AccountDat
|
|||||||
return docTree, nil
|
return docTree, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func BuildDocTree(t treestorage.TreeStorage, decoder keys.Decoder, listener TreeUpdateListener, aclTree ACLTree) (DocTree, error) {
|
func BuildDocTree(t treestorage.TreeStorage, decoder keys.Decoder, listener TreeUpdateListener, aclList list.ACLList) (DocTree, error) {
|
||||||
treeBuilder := newTreeBuilder(t, decoder)
|
treeBuilder := newTreeBuilder(t, decoder)
|
||||||
validator := newTreeValidator()
|
validator := newTreeValidator()
|
||||||
|
|
||||||
@ -99,7 +127,7 @@ func BuildDocTree(t treestorage.TreeStorage, decoder keys.Decoder, listener Tree
|
|||||||
notSeenIdxBuf: make([]int, 0, 10),
|
notSeenIdxBuf: make([]int, 0, 10),
|
||||||
identityKeys: make(map[string]signingkey.PubKey),
|
identityKeys: make(map[string]signingkey.PubKey),
|
||||||
}
|
}
|
||||||
err := docTree.rebuildFromStorage(aclTree, nil)
|
err := docTree.rebuildFromStorage(aclList, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -119,7 +147,7 @@ func BuildDocTree(t treestorage.TreeStorage, decoder keys.Decoder, listener Tree
|
|||||||
return docTree, nil
|
return docTree, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *docTree) rebuildFromStorage(aclTree ACLTree, newChanges []*Change) (err error) {
|
func (d *docTree) rebuildFromStorage(aclList list.ACLList, newChanges []*Change) (err error) {
|
||||||
d.treeBuilder.Init(d.identityKeys)
|
d.treeBuilder.Init(d.identityKeys)
|
||||||
|
|
||||||
d.tree, err = d.treeBuilder.Build(false, newChanges)
|
d.tree, err = d.treeBuilder.Build(false, newChanges)
|
||||||
@ -127,7 +155,7 @@ func (d *docTree) rebuildFromStorage(aclTree ACLTree, newChanges []*Change) (err
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return d.validator.ValidateTree(d.tree, aclTree)
|
return d.validator.ValidateTree(d.tree, aclList)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *docTree) ID() string {
|
func (d *docTree) ID() string {
|
||||||
@ -142,7 +170,7 @@ func (d *docTree) Storage() treestorage.TreeStorage {
|
|||||||
return d.treeStorage
|
return d.treeStorage
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *docTree) AddContent(ctx context.Context, aclTree ACLTree, content proto.Marshaler, isSnapshot bool) (*aclpb.RawChange, error) {
|
func (d *docTree) AddContent(ctx context.Context, aclList list.ACLList, content proto.Marshaler, isSnapshot bool) (*aclpb.RawChange, error) {
|
||||||
if d.accountData == nil {
|
if d.accountData == nil {
|
||||||
return nil, ErrTreeWithoutIdentity
|
return nil, ErrTreeWithoutIdentity
|
||||||
}
|
}
|
||||||
@ -153,12 +181,12 @@ func (d *docTree) AddContent(ctx context.Context, aclTree ACLTree, content proto
|
|||||||
d.updateListener.Update(d)
|
d.updateListener.Update(d)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
state := aclTree.ACLState()
|
state := aclList.ACLState()
|
||||||
change := &aclpb.Change{
|
change := &aclpb.Change{
|
||||||
TreeHeadIds: d.tree.Heads(),
|
TreeHeadIds: d.tree.Heads(),
|
||||||
AclHeadIds: aclTree.Heads(),
|
AclHeadId: aclList.Last().Id,
|
||||||
SnapshotBaseId: d.tree.RootId(),
|
SnapshotBaseId: d.tree.RootId(),
|
||||||
CurrentReadKeyHash: state.currentReadKeyHash,
|
CurrentReadKeyHash: state.CurrentReadKeyHash(),
|
||||||
Timestamp: int64(time.Now().Nanosecond()),
|
Timestamp: int64(time.Now().Nanosecond()),
|
||||||
Identity: d.accountData.Identity,
|
Identity: d.accountData.Identity,
|
||||||
IsSnapshot: isSnapshot,
|
IsSnapshot: isSnapshot,
|
||||||
@ -168,7 +196,7 @@ func (d *docTree) AddContent(ctx context.Context, aclTree ACLTree, content proto
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
encrypted, err := state.userReadKeys[state.currentReadKeyHash].Encrypt(marshalledData)
|
encrypted, err := state.UserReadKeys()[state.CurrentReadKeyHash()].Encrypt(marshalledData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -213,7 +241,7 @@ func (d *docTree) AddContent(ctx context.Context, aclTree ACLTree, content proto
|
|||||||
return rawCh, nil
|
return rawCh, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *docTree) AddRawChanges(ctx context.Context, aclTree ACLTree, rawChanges ...*aclpb.RawChange) (addResult AddResult, err error) {
|
func (d *docTree) AddRawChanges(ctx context.Context, aclList list.ACLList, rawChanges ...*aclpb.RawChange) (addResult AddResult, err error) {
|
||||||
var mode Mode
|
var mode Mode
|
||||||
|
|
||||||
// resetting buffers
|
// resetting buffers
|
||||||
@ -300,7 +328,7 @@ func (d *docTree) AddRawChanges(ctx context.Context, aclTree ACLTree, rawChanges
|
|||||||
// checking if we have some changes with different snapshot and then rebuilding
|
// checking if we have some changes with different snapshot and then rebuilding
|
||||||
for _, ch := range d.tmpChangesBuf {
|
for _, ch := range d.tmpChangesBuf {
|
||||||
if ch.SnapshotId != d.tree.RootId() && ch.SnapshotId != "" {
|
if ch.SnapshotId != d.tree.RootId() && ch.SnapshotId != "" {
|
||||||
err = d.rebuildFromStorage(aclTree, d.tmpChangesBuf)
|
err = d.rebuildFromStorage(aclList, d.tmpChangesBuf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return AddResult{}, err
|
return AddResult{}, err
|
||||||
}
|
}
|
||||||
@ -331,7 +359,7 @@ func (d *docTree) AddRawChanges(ctx context.Context, aclTree ACLTree, rawChanges
|
|||||||
default:
|
default:
|
||||||
// just rebuilding the state from start without reloading everything from tree storage
|
// just rebuilding the state from start without reloading everything from tree storage
|
||||||
// as an optimization we could've started from current heads, but I didn't implement that
|
// as an optimization we could've started from current heads, but I didn't implement that
|
||||||
err = d.validator.ValidateTree(d.tree, aclTree)
|
err = d.validator.ValidateTree(d.tree, aclList)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// rolling back
|
// rolling back
|
||||||
for _, ch := range d.tmpChangesBuf {
|
for _, ch := range d.tmpChangesBuf {
|
||||||
@ -366,8 +394,7 @@ func (d *docTree) IterateFrom(s string, f func(change *Change) bool) {
|
|||||||
func (d *docTree) HasChange(s string) bool {
|
func (d *docTree) HasChange(s string) bool {
|
||||||
_, attachedExists := d.tree.attached[s]
|
_, attachedExists := d.tree.attached[s]
|
||||||
_, unattachedExists := d.tree.unAttached[s]
|
_, unattachedExists := d.tree.unAttached[s]
|
||||||
_, invalidExists := d.tree.invalidChanges[s]
|
return attachedExists || unattachedExists
|
||||||
return attachedExists || unattachedExists || invalidExists
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *docTree) Heads() []string {
|
func (d *docTree) Heads() []string {
|
||||||
@ -462,3 +489,31 @@ func (d *docTree) ChangesAfterCommonSnapshot(theirPath []string) ([]*aclpb.RawCh
|
|||||||
func (d *docTree) DebugDump() (string, error) {
|
func (d *docTree) DebugDump() (string, error) {
|
||||||
return d.tree.Graph(NoOpDescriptionParser)
|
return d.tree.Graph(NoOpDescriptionParser)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func commonSnapshotForTwoPaths(ourPath []string, theirPath []string) (string, error) {
|
||||||
|
var i int
|
||||||
|
var j int
|
||||||
|
log.With(zap.Strings("our path", ourPath), zap.Strings("their path", theirPath)).
|
||||||
|
Debug("finding common snapshot for two paths")
|
||||||
|
OuterLoop:
|
||||||
|
// find starting point from the right
|
||||||
|
for i = len(ourPath) - 1; i >= 0; i-- {
|
||||||
|
for j = len(theirPath) - 1; j >= 0; j-- {
|
||||||
|
// most likely there would be only one comparison, because mostly the snapshot path will start from the root for nodes
|
||||||
|
if ourPath[i] == theirPath[j] {
|
||||||
|
break OuterLoop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if i < 0 || j < 0 {
|
||||||
|
return "", ErrNoCommonSnapshot
|
||||||
|
}
|
||||||
|
// find last common element of the sequence moving from right to left
|
||||||
|
for i >= 0 && j >= 0 {
|
||||||
|
if ourPath[i] == theirPath[j] {
|
||||||
|
i--
|
||||||
|
j--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ourPath[i+1], nil
|
||||||
|
}
|
||||||
|
|||||||
@ -3,65 +3,66 @@ package tree
|
|||||||
import (
|
import (
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/account"
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/account"
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||||
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/list"
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treestorage"
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treestorage"
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treestorage/treepb"
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treestorage/treepb"
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/cid"
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/cid"
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/signingkey"
|
|
||||||
"github.com/gogo/protobuf/proto"
|
"github.com/gogo/protobuf/proto"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func CreateNewTreeStorageWithACL(
|
//
|
||||||
acc *account.AccountData,
|
//func CreateNewTreeStorageWithACL(
|
||||||
build func(builder ACLChangeBuilder) error,
|
// acc *account.AccountData,
|
||||||
create treestorage.CreatorFunc) (treestorage.TreeStorage, error) {
|
// build func(builder list.ACLChangeBuilder) error,
|
||||||
bld := newACLChangeBuilder()
|
// create treestorage.CreatorFunc) (treestorage.TreeStorage, error) {
|
||||||
bld.Init(
|
// bld := list.newACLChangeBuilder()
|
||||||
newACLStateWithIdentity(acc.Identity, acc.EncKey, signingkey.NewEd25519PubKeyDecoder()),
|
// bld.Init(
|
||||||
&Tree{},
|
// list.newACLStateWithIdentity(acc.Identity, acc.EncKey, signingkey.NewEd25519PubKeyDecoder()),
|
||||||
acc)
|
// &Tree{},
|
||||||
err := build(bld)
|
// acc)
|
||||||
if err != nil {
|
// err := build(bld)
|
||||||
return nil, err
|
// if err != nil {
|
||||||
}
|
// return nil, err
|
||||||
|
// }
|
||||||
change, payload, err := bld.BuildAndApply()
|
//
|
||||||
if err != nil {
|
// change, payload, err := bld.BuildAndApply()
|
||||||
return nil, err
|
// if err != nil {
|
||||||
}
|
// return nil, err
|
||||||
|
// }
|
||||||
rawChange := &aclpb.RawChange{
|
//
|
||||||
Payload: payload,
|
// rawChange := &aclpb.RawChange{
|
||||||
Signature: change.Signature(),
|
// Payload: payload,
|
||||||
Id: change.CID(),
|
// Signature: change.Signature(),
|
||||||
}
|
// Id: change.CID(),
|
||||||
header, id, err := createTreeHeaderAndId(rawChange, treepb.TreeHeader_ACLTree, "")
|
// }
|
||||||
if err != nil {
|
// header, id, err := createTreeHeaderAndId(rawChange, treepb.TreeHeader_ACLTree, "")
|
||||||
return nil, err
|
// if err != nil {
|
||||||
}
|
// return nil, err
|
||||||
|
// }
|
||||||
thr, err := create(id, header, []*aclpb.RawChange{rawChange})
|
//
|
||||||
if err != nil {
|
// thr, err := create(id, header, []*aclpb.RawChange{rawChange})
|
||||||
return nil, err
|
// if err != nil {
|
||||||
}
|
// return nil, err
|
||||||
|
// }
|
||||||
err = thr.SetHeads([]string{change.CID()})
|
//
|
||||||
if err != nil {
|
// err = thr.SetHeads([]string{change.CID()})
|
||||||
return nil, err
|
// if err != nil {
|
||||||
}
|
// return nil, err
|
||||||
return thr, nil
|
// }
|
||||||
}
|
// return thr, nil
|
||||||
|
//}
|
||||||
|
|
||||||
func CreateNewTreeStorage(
|
func CreateNewTreeStorage(
|
||||||
acc *account.AccountData,
|
acc *account.AccountData,
|
||||||
aclTree ACLTree,
|
aclList list.ACLList,
|
||||||
content proto.Marshaler,
|
content proto.Marshaler,
|
||||||
create treestorage.CreatorFunc) (treestorage.TreeStorage, error) {
|
create treestorage.CreatorFunc) (treestorage.TreeStorage, error) {
|
||||||
|
|
||||||
state := aclTree.ACLState()
|
state := aclList.ACLState()
|
||||||
change := &aclpb.Change{
|
change := &aclpb.Change{
|
||||||
AclHeadIds: aclTree.Heads(),
|
AclHeadId: aclList.Last().Id,
|
||||||
CurrentReadKeyHash: state.currentReadKeyHash,
|
CurrentReadKeyHash: state.CurrentReadKeyHash(),
|
||||||
Timestamp: int64(time.Now().Nanosecond()),
|
Timestamp: int64(time.Now().Nanosecond()),
|
||||||
Identity: acc.Identity,
|
Identity: acc.Identity,
|
||||||
IsSnapshot: true,
|
IsSnapshot: true,
|
||||||
@ -71,7 +72,7 @@ func CreateNewTreeStorage(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
encrypted, err := state.userReadKeys[state.currentReadKeyHash].Encrypt(marshalledData)
|
encrypted, err := state.UserReadKeys()[state.CurrentReadKeyHash()].Encrypt(marshalledData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -95,7 +96,7 @@ func CreateNewTreeStorage(
|
|||||||
Signature: signature,
|
Signature: signature,
|
||||||
Id: changeId,
|
Id: changeId,
|
||||||
}
|
}
|
||||||
header, treeId, err := createTreeHeaderAndId(rawChange, treepb.TreeHeader_DocTree, aclTree.ID())
|
header, treeId, err := createTreeHeaderAndId(rawChange, treepb.TreeHeader_DocTree, aclList.ID())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import (
|
|||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/app"
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/app"
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/app/logger"
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/app/logger"
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||||
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/list"
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/testutils/testchanges/testchangepb"
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/testutils/testchanges/testchangepb"
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/tree"
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/tree"
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treestorage/treepb"
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treestorage/treepb"
|
||||||
@ -131,7 +132,7 @@ func (s *service) CreateACLTree(ctx context.Context) (id string, err error) {
|
|||||||
heads []string
|
heads []string
|
||||||
)
|
)
|
||||||
|
|
||||||
t, err := tree.CreateNewTreeStorageWithACL(acc, func(builder tree.ACLChangeBuilder) error {
|
t, err := tree.CreateNewTreeStorageWithACL(acc, func(builder list.ACLChangeBuilder) error {
|
||||||
err := builder.UserAdd(acc.Identity, acc.EncKey.GetPublic(), aclpb.ACLChange_Admin)
|
err := builder.UserAdd(acc.Identity, acc.EncKey.GetPublic(), aclpb.ACLChange_Admin)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user