Add acltree in new package
This commit is contained in:
parent
307f517ee5
commit
110ec01b0c
@ -50,11 +50,18 @@ func newACLState() *ACLState {
|
||||
}
|
||||
}
|
||||
|
||||
func (st *ACLState) applyChange(change *aclpb.Change) (err error) {
|
||||
func (st *ACLState) applyChange(changeWrapper *Change) (err error) {
|
||||
change := changeWrapper.Content
|
||||
aclData := &aclpb.ACLChangeACLData{}
|
||||
err = proto.Unmarshal(change.ChangesData, aclData)
|
||||
if err != nil {
|
||||
return
|
||||
|
||||
if changeWrapper.DecryptedModel != nil {
|
||||
aclData = changeWrapper.DecryptedModel.(*aclpb.ACLChangeACLData)
|
||||
} else {
|
||||
err = proto.Unmarshal(change.ChangesData, aclData)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
changeWrapper.DecryptedModel = aclData
|
||||
}
|
||||
|
||||
defer func() {
|
||||
|
||||
79
pkg/acl/tree/aclstatebuilder.go
Normal file
79
pkg/acl/tree/aclstatebuilder.go
Normal file
@ -0,0 +1,79 @@
|
||||
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
|
||||
}
|
||||
|
||||
sb.tree.IterateSkip(sb.tree.root.Id, startChange.Id, func(c *Change) (isContinue bool) {
|
||||
err = state.applyChange(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
|
||||
})
|
||||
|
||||
return state, foundId, err
|
||||
}
|
||||
466
pkg/acl/tree/acltree.go
Normal file
466
pkg/acl/tree/acltree.go
Normal file
@ -0,0 +1,466 @@
|
||||
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"
|
||||
"go.uber.org/zap"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type AddResultSummary int
|
||||
|
||||
var ErrTreeWithoutIdentity = errors.New("acl tree is created without identity")
|
||||
|
||||
const (
|
||||
AddResultSummaryNothing AddResultSummary = iota
|
||||
AddResultSummaryAppend
|
||||
AddResultSummaryRebuild
|
||||
)
|
||||
|
||||
type AddResult struct {
|
||||
OldHeads []string
|
||||
Heads []string
|
||||
Added []*aclpb.RawChange
|
||||
// TODO: add summary for changes
|
||||
Summary AddResultSummary
|
||||
}
|
||||
|
||||
type TreeUpdateListener 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
|
||||
ID() string
|
||||
Header() *treepb.TreeHeader
|
||||
ACLState() *ACLState
|
||||
AddContent(ctx context.Context, f func(builder ChangeBuilder) error) (*aclpb.RawChange, error)
|
||||
AddRawChanges(ctx context.Context, changes ...*aclpb.RawChange) (AddResult, error)
|
||||
Heads() []string
|
||||
Root() *Change
|
||||
Iterate(func(change *Change) bool)
|
||||
IterateFrom(string, func(change *Change) bool)
|
||||
HasChange(string) bool
|
||||
SnapshotPath() []string
|
||||
ChangesAfterCommonSnapshot(snapshotPath []string) ([]*aclpb.RawChange, error)
|
||||
Storage() treestorage.TreeStorage
|
||||
DebugDump() (string, error)
|
||||
|
||||
Close() error
|
||||
}
|
||||
|
||||
type aclTree struct {
|
||||
treeStorage treestorage.TreeStorage
|
||||
accountData *account.AccountData
|
||||
updateListener TreeUpdateListener
|
||||
|
||||
id string
|
||||
header *treepb.TreeHeader
|
||||
tree *Tree
|
||||
aclState *ACLState
|
||||
|
||||
treeBuilder *treeBuilder
|
||||
aclStateBuilder *aclStateBuilder
|
||||
changeBuilder *changeBuilder
|
||||
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
func BuildACLTreeWithIdentity(
|
||||
t treestorage.TreeStorage,
|
||||
acc *account.AccountData,
|
||||
listener TreeUpdateListener) (ACLTree, error) {
|
||||
treeBuilder := newTreeBuilder(t, acc.Decoder)
|
||||
aclStateBuilder := newACLStateBuilderWithIdentity(acc.Decoder, acc)
|
||||
changeBuilder := newChangeBuilder()
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
listener.Rebuild(aclTree)
|
||||
|
||||
return aclTree, nil
|
||||
}
|
||||
|
||||
func BuildACLTree(t treestorage.TreeStorage) {
|
||||
// TODO: Add logic for building without identity
|
||||
}
|
||||
|
||||
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 ChangeBuilder) 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)
|
||||
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] ...
|
||||
for _, ch := range rawChanges {
|
||||
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)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = a.removeOrphans()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = a.treeStorage.SetHeads(a.tree.Heads())
|
||||
if err != 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 _, ch := range rawChanges {
|
||||
if _, exists := a.tree.attached[ch.Id]; exists {
|
||||
added = append(added, ch)
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
prevHeads := a.tree.Heads()
|
||||
mode = a.tree.Add(changes...)
|
||||
switch mode {
|
||||
case Nothing:
|
||||
return AddResult{
|
||||
OldHeads: prevHeads,
|
||||
Heads: prevHeads,
|
||||
Summary: AddResultSummaryNothing,
|
||||
}, nil
|
||||
|
||||
case Rebuild:
|
||||
err = a.rebuildFromStorage()
|
||||
if err != nil {
|
||||
return AddResult{}, err
|
||||
}
|
||||
|
||||
return AddResult{
|
||||
OldHeads: prevHeads,
|
||||
Heads: a.tree.Heads(),
|
||||
Added: getAddedChanges(),
|
||||
Summary: AddResultSummaryRebuild,
|
||||
}, nil
|
||||
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 = a.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
|
||||
}
|
||||
|
||||
aclChange, err := a.treeBuilder.makeUnverifiedACLChange(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ch := NewChange(id, aclChange)
|
||||
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 (a *aclTree) 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
|
||||
}
|
||||
@ -22,12 +22,18 @@ type Change struct {
|
||||
Id string
|
||||
SnapshotId string
|
||||
IsSnapshot bool
|
||||
DecryptedChange []byte
|
||||
DecryptedChange []byte // TODO: check if we need it
|
||||
DecryptedModel interface{}
|
||||
|
||||
Content *aclpb.Change
|
||||
Sign []byte
|
||||
}
|
||||
|
||||
func (ch *Change) ProtoChange() *aclpb.ACLChange {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (ch *Change) DecryptContents(key *symmetric.Key) error {
|
||||
// if the document is already decrypted
|
||||
if ch.Content.CurrentReadKeyHash == 0 {
|
||||
@ -65,10 +71,6 @@ func NewChange(id string, ch *aclpb.Change) *Change {
|
||||
}
|
||||
}
|
||||
|
||||
func (ch *Change) ProtoChange() *aclpb.Change {
|
||||
return ch.Content
|
||||
}
|
||||
|
||||
func (ch *Change) DecryptedChangeContent() []byte {
|
||||
return ch.DecryptedChange
|
||||
}
|
||||
|
||||
163
pkg/acl/tree/changebuilder.go
Normal file
163
pkg/acl/tree/changebuilder.go
Normal file
@ -0,0 +1,163 @@
|
||||
package tree
|
||||
|
||||
import (
|
||||
"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/cid"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/encryptionkey"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/symmetric"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"hash/fnv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type MarshalledChange = []byte
|
||||
|
||||
type ACLChangeBuilder interface {
|
||||
UserAdd(identity string, encryptionKey encryptionkey.PubKey, permissions aclpb.ACLChangeUserPermissions) error
|
||||
AddId(id string) // TODO: this is only for testing
|
||||
SetMakeSnapshot(bool) // TODO: who should decide this? probably ACLTree so we can delete it
|
||||
}
|
||||
|
||||
type ChangeBuilder interface {
|
||||
ACLChangeBuilder
|
||||
AddChangeContent(marshaler proto.Marshaler) // user code should be responsible for making regular snapshots
|
||||
}
|
||||
|
||||
type changeBuilder struct {
|
||||
aclState *ACLState
|
||||
tree *Tree
|
||||
acc *account.AccountData
|
||||
|
||||
aclData *aclpb.ACLChangeACLData
|
||||
changeContent proto.Marshaler
|
||||
id string
|
||||
makeSnapshot bool
|
||||
readKey *symmetric.Key
|
||||
readKeyHash uint64
|
||||
}
|
||||
|
||||
func newChangeBuilder() *changeBuilder {
|
||||
return &changeBuilder{}
|
||||
}
|
||||
|
||||
func (c *changeBuilder) Init(state *ACLState, tree *Tree, acc *account.AccountData) {
|
||||
c.aclState = state
|
||||
c.tree = tree
|
||||
c.acc = acc
|
||||
|
||||
c.aclData = &aclpb.ACLChangeACLData{}
|
||||
// setting read key for further encryption etc
|
||||
if state.currentReadKeyHash == 0 {
|
||||
c.readKey, _ = symmetric.NewRandom()
|
||||
|
||||
hasher := fnv.New64()
|
||||
hasher.Write(c.readKey.Bytes())
|
||||
c.readKeyHash = hasher.Sum64()
|
||||
} else {
|
||||
c.readKey = c.aclState.userReadKeys[c.aclState.currentReadKeyHash]
|
||||
c.readKeyHash = c.aclState.currentReadKeyHash
|
||||
}
|
||||
}
|
||||
|
||||
func (c *changeBuilder) AddId(id string) {
|
||||
c.id = id
|
||||
}
|
||||
|
||||
func (c *changeBuilder) SetMakeSnapshot(b bool) {
|
||||
c.makeSnapshot = b
|
||||
}
|
||||
|
||||
func (c *changeBuilder) UserAdd(identity string, encryptionKey encryptionkey.PubKey, permissions aclpb.ACLChangeUserPermissions) error {
|
||||
var allKeys []*symmetric.Key
|
||||
if c.aclState.currentReadKeyHash != 0 {
|
||||
for _, key := range c.aclState.userReadKeys {
|
||||
allKeys = append(allKeys, key)
|
||||
}
|
||||
} else {
|
||||
allKeys = append(allKeys, c.readKey)
|
||||
}
|
||||
|
||||
var encryptedKeys [][]byte
|
||||
for _, k := range allKeys {
|
||||
res, err := encryptionKey.Encrypt(k.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
encryptedKeys = append(encryptedKeys, res)
|
||||
}
|
||||
rawKey, err := encryptionKey.Raw()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ch := &aclpb.ACLChangeACLContentValue{
|
||||
Value: &aclpb.ACLChangeACLContentValueValueOfUserAdd{
|
||||
UserAdd: &aclpb.ACLChangeUserAdd{
|
||||
Identity: identity,
|
||||
EncryptionKey: rawKey,
|
||||
EncryptedReadKeys: encryptedKeys,
|
||||
Permissions: permissions,
|
||||
},
|
||||
},
|
||||
}
|
||||
c.aclData.AclContent = append(c.aclData.AclContent, ch)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *changeBuilder) BuildAndApply() (*Change, []byte, error) {
|
||||
aclChange := &aclpb.ACLChange{
|
||||
TreeHeadIds: c.tree.Heads(),
|
||||
AclHeadIds: c.tree.ACLHeads(),
|
||||
SnapshotBaseId: c.tree.RootId(),
|
||||
AclData: c.aclData,
|
||||
CurrentReadKeyHash: c.readKeyHash,
|
||||
Timestamp: int64(time.Now().Nanosecond()),
|
||||
Identity: c.acc.Identity,
|
||||
}
|
||||
err := c.aclState.applyChange(aclChange)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if c.makeSnapshot {
|
||||
c.aclData.AclSnapshot = c.aclState.makeSnapshot()
|
||||
}
|
||||
|
||||
var marshalled []byte
|
||||
if c.changeContent != nil {
|
||||
marshalled, err = c.changeContent.Marshal()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
encrypted, err := c.aclState.userReadKeys[c.aclState.currentReadKeyHash].
|
||||
Encrypt(marshalled)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
aclChange.ChangesData = encrypted
|
||||
}
|
||||
|
||||
fullMarshalledChange, err := proto.Marshal(aclChange)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
signature, err := c.acc.SignKey.Sign(fullMarshalledChange)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
id, err := cid.NewCIDFromBytes(fullMarshalledChange)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
ch := NewChange(id, aclChange)
|
||||
ch.DecryptedDocumentChange = marshalled
|
||||
ch.Sign = signature
|
||||
|
||||
return ch, fullMarshalledChange, nil
|
||||
}
|
||||
|
||||
func (c *changeBuilder) AddChangeContent(marshaler proto.Marshaler) {
|
||||
c.changeContent = marshaler
|
||||
}
|
||||
45
pkg/acl/tree/descriptionparser.go
Normal file
45
pkg/acl/tree/descriptionparser.go
Normal file
@ -0,0 +1,45 @@
|
||||
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 {
|
||||
ParseChange(*Change) ([]string, error)
|
||||
}
|
||||
|
||||
var ACLDescriptionParser = aclDescriptionParser{}
|
||||
|
||||
type aclDescriptionParser struct{}
|
||||
|
||||
func (a aclDescriptionParser) ParseChange(changeWrapper *Change) (res []string, err error) {
|
||||
change := changeWrapper.Content
|
||||
aclData := &aclpb.ACLChangeACLData{}
|
||||
|
||||
if changeWrapper.DecryptedModel != nil {
|
||||
aclData = changeWrapper.DecryptedModel.(*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
|
||||
}
|
||||
@ -4,7 +4,6 @@ import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/slice"
|
||||
"sort"
|
||||
)
|
||||
|
||||
@ -289,34 +288,6 @@ func (t *Tree) updateHeads() {
|
||||
sort.Strings(t.metaHeadIds)
|
||||
}
|
||||
|
||||
func (t *Tree) ACLHeads() []string {
|
||||
var aclTreeHeads []string
|
||||
for _, head := range t.Heads() {
|
||||
if slice.FindPos(aclTreeHeads, head) != -1 { // do not scan known heads
|
||||
continue
|
||||
}
|
||||
precedingHeads := t.getPrecedingACLHeads(head)
|
||||
|
||||
for _, aclHead := range precedingHeads {
|
||||
if slice.FindPos(aclTreeHeads, aclHead) != -1 {
|
||||
continue
|
||||
}
|
||||
aclTreeHeads = append(aclTreeHeads, aclHead)
|
||||
}
|
||||
}
|
||||
return aclTreeHeads
|
||||
}
|
||||
|
||||
func (t *Tree) getPrecedingACLHeads(head string) []string {
|
||||
headChange := t.attached[head]
|
||||
|
||||
if headChange.Content.GetAclData() != nil {
|
||||
return []string{head}
|
||||
} else {
|
||||
return headChange.Content.AclHeadIds
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Tree) iterate(start *Change, f func(c *Change) (isContinue bool)) {
|
||||
it := newIterator()
|
||||
defer freeIterator(it)
|
||||
|
||||
@ -223,8 +223,8 @@ func (tb *treeBuilder) makeVerifiedChange(change *aclpb.RawChange) (aclChange *a
|
||||
return
|
||||
}
|
||||
|
||||
func (tb *treeBuilder) makeUnverifiedACLChange(change *aclpb.RawChange) (aclChange *aclpb.ACLChange, err error) {
|
||||
aclChange = new(aclpb.ACLChange)
|
||||
func (tb *treeBuilder) makeUnverifiedACLChange(change *aclpb.RawChange) (aclChange *aclpb.Change, err error) {
|
||||
aclChange = new(aclpb.Change)
|
||||
err = proto.Unmarshal(change.Payload, aclChange)
|
||||
return
|
||||
}
|
||||
|
||||
11
pkg/acl/tree/treegraph.go
Normal file
11
pkg/acl/tree/treegraph.go
Normal file
@ -0,0 +1,11 @@
|
||||
//go:build ((!linux && !darwin) || android || ios || nographviz) && !amd64
|
||||
// +build !linux,!darwin android ios nographviz
|
||||
// +build !amd64
|
||||
|
||||
package tree
|
||||
|
||||
import "fmt"
|
||||
|
||||
func (t *Tree) Graph(parser DescriptionParser) (data []string, err error) {
|
||||
return "", fmt.Errorf("not supported")
|
||||
}
|
||||
121
pkg/acl/tree/treegraph_nix.go
Normal file
121
pkg/acl/tree/treegraph_nix.go
Normal file
@ -0,0 +1,121 @@
|
||||
//go:build (linux || darwin) && !android && !ios && !nographviz && (amd64 || arm64)
|
||||
// +build linux darwin
|
||||
// +build !android
|
||||
// +build !ios
|
||||
// +build !nographviz
|
||||
// +build amd64 arm64
|
||||
|
||||
package tree
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/goccy/go-graphviz"
|
||||
"github.com/goccy/go-graphviz/cgraph"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (t *Tree) Graph(parser DescriptionParser) (data string, err error) {
|
||||
var order = make(map[string]string)
|
||||
var seq = 0
|
||||
t.Iterate(t.RootId(), func(c *Change) (isContinue bool) {
|
||||
v := order[c.Id]
|
||||
if v == "" {
|
||||
order[c.Id] = fmt.Sprint(seq)
|
||||
} else {
|
||||
order[c.Id] = fmt.Sprintf("%s,%d", v, seq)
|
||||
}
|
||||
seq++
|
||||
return true
|
||||
})
|
||||
g := graphviz.New()
|
||||
defer g.Close()
|
||||
graph, err := g.Graph()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
err = graph.Close()
|
||||
}()
|
||||
var nodes = make(map[string]*cgraph.Node)
|
||||
var addChange = func(c *Change) error {
|
||||
n, e := graph.CreateNode(c.Id)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
n.SetStyle(cgraph.FilledNodeStyle)
|
||||
nodes[c.Id] = n
|
||||
ord := order[c.Id]
|
||||
if ord == "" {
|
||||
ord = "miss"
|
||||
}
|
||||
chSymbs, err := parser.ParseChange(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
shortId := c.Id
|
||||
label := fmt.Sprintf("Id: %s\nOrd: %s\nTime: %s\nChanges: %s\n",
|
||||
shortId,
|
||||
ord,
|
||||
time.Unix(c.Content.Timestamp, 0).Format("02.01.06 15:04:05"),
|
||||
strings.Join(chSymbs, ","),
|
||||
)
|
||||
n.SetLabel(label)
|
||||
return nil
|
||||
}
|
||||
for _, c := range t.attached {
|
||||
if err = addChange(c); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
for _, c := range t.unAttached {
|
||||
if err = addChange(c); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
var getNode = func(id string) (*cgraph.Node, error) {
|
||||
if n, ok := nodes[id]; ok {
|
||||
return n, nil
|
||||
}
|
||||
n, err := graph.CreateNode(fmt.Sprintf("%s: not in Tree", id))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nodes[id] = n
|
||||
return n, nil
|
||||
}
|
||||
var addLinks = func(c *Change) error {
|
||||
for _, prevId := range c.PreviousIds {
|
||||
self, e := getNode(c.Id)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
prev, e := getNode(prevId)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
_, e = graph.CreateEdge("", self, prev)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
for _, c := range t.attached {
|
||||
if err = addLinks(c); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
for _, c := range t.unAttached {
|
||||
if err = addLinks(c); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
if err = g.Render(graph, "dot", &buf); err != nil {
|
||||
return
|
||||
}
|
||||
return buf.String(), nil
|
||||
}
|
||||
158
pkg/acl/tree/treeiterator.go
Normal file
158
pkg/acl/tree/treeiterator.go
Normal file
@ -0,0 +1,158 @@
|
||||
package tree
|
||||
|
||||
import "sync"
|
||||
|
||||
var itPool = &sync.Pool{
|
||||
New: func() interface{} {
|
||||
return &iterator{}
|
||||
},
|
||||
}
|
||||
|
||||
func newIterator() *iterator {
|
||||
return itPool.Get().(*iterator)
|
||||
}
|
||||
|
||||
func freeIterator(i *iterator) {
|
||||
itPool.Put(i)
|
||||
}
|
||||
|
||||
type iterator struct {
|
||||
compBuf []*Change
|
||||
queue []*Change
|
||||
doneMap map[*Change]struct{}
|
||||
breakpoint *Change
|
||||
f func(c *Change) bool
|
||||
}
|
||||
|
||||
func (i *iterator) iterateSkip(start *Change, skipBefore *Change, f func(c *Change) (isContinue bool)) {
|
||||
skipping := true
|
||||
i.iterate(start, func(c *Change) (isContinue bool) {
|
||||
if skipping && c != skipBefore {
|
||||
return true
|
||||
}
|
||||
skipping = false
|
||||
return f(c)
|
||||
})
|
||||
}
|
||||
|
||||
func (i *iterator) iterate(start *Change, f func(c *Change) (isContinue bool)) {
|
||||
if start == nil {
|
||||
return
|
||||
}
|
||||
// reset
|
||||
i.queue = i.queue[:0]
|
||||
i.compBuf = i.compBuf[:0]
|
||||
i.doneMap = make(map[*Change]struct{})
|
||||
i.queue = append(i.queue, start)
|
||||
i.breakpoint = nil
|
||||
i.f = f
|
||||
|
||||
for len(i.queue) > 0 {
|
||||
c := i.queue[0]
|
||||
i.queue = i.queue[1:]
|
||||
nl := len(c.Next)
|
||||
if nl == 1 {
|
||||
if !i.iterateLin(c) {
|
||||
return
|
||||
}
|
||||
if i.breakpoint != nil {
|
||||
i.toQueue(i.breakpoint)
|
||||
i.breakpoint = nil
|
||||
}
|
||||
} else {
|
||||
_, done := i.doneMap[c]
|
||||
if !done {
|
||||
if !f(c) {
|
||||
return
|
||||
}
|
||||
i.doneMap[c] = struct{}{}
|
||||
}
|
||||
if nl != 0 {
|
||||
for _, next := range c.Next {
|
||||
i.toQueue(next)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (i *iterator) iterateLin(c *Change) bool {
|
||||
for len(c.Next) == 1 {
|
||||
_, done := i.doneMap[c]
|
||||
if !done {
|
||||
if !i.f(c) {
|
||||
return false
|
||||
}
|
||||
i.doneMap[c] = struct{}{}
|
||||
}
|
||||
|
||||
c = c.Next[0]
|
||||
if len(c.PreviousIds) > 1 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(c.Next) == 0 && len(c.PreviousIds) <= 1 {
|
||||
if !i.f(c) {
|
||||
return false
|
||||
}
|
||||
i.doneMap[c] = struct{}{}
|
||||
} else {
|
||||
i.breakpoint = c
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (i *iterator) comp(c1, c2 *Change) uint8 {
|
||||
if c1.Id == c2.Id {
|
||||
return 0
|
||||
}
|
||||
i.compBuf = i.compBuf[:0]
|
||||
i.compBuf = append(i.compBuf, c1.Next...)
|
||||
var uniq = make(map[*Change]struct{})
|
||||
var appendUniqueToBuf = func(next []*Change) {
|
||||
for _, n := range next {
|
||||
if _, ok := uniq[n]; !ok {
|
||||
i.compBuf = append(i.compBuf, n)
|
||||
uniq[n] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
var used int
|
||||
for len(i.compBuf)-used > 0 {
|
||||
l := len(i.compBuf) - used
|
||||
for _, n := range i.compBuf[used:] {
|
||||
delete(uniq, n)
|
||||
if n.Id == c2.Id {
|
||||
return 1
|
||||
} else {
|
||||
appendUniqueToBuf(n.Next)
|
||||
}
|
||||
}
|
||||
used += l
|
||||
}
|
||||
return 2
|
||||
}
|
||||
|
||||
func (i *iterator) toQueue(c *Change) {
|
||||
var pos = -1
|
||||
For:
|
||||
for idx, qc := range i.queue {
|
||||
switch i.comp(c, qc) {
|
||||
// exists
|
||||
case 0:
|
||||
return
|
||||
//
|
||||
case 1:
|
||||
pos = idx
|
||||
break For
|
||||
}
|
||||
}
|
||||
if pos == -1 {
|
||||
i.queue = append(i.queue, c)
|
||||
} else if pos == 0 {
|
||||
i.queue = append([]*Change{c}, i.queue...)
|
||||
} else {
|
||||
i.queue = append(i.queue[:pos], append([]*Change{c}, i.queue[pos:]...)...)
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user