diff --git a/pkg/acl/list/aclstate.go b/pkg/acl/list/aclstate.go index e15b3ec6..0424dc42 100644 --- a/pkg/acl/list/aclstate.go +++ b/pkg/acl/list/aclstate.go @@ -56,12 +56,13 @@ func newACLStateWithIdentity( } } -func newACLState() *ACLState { +func newACLState(decoder keys.Decoder) *ACLState { return &ACLState{ - userReadKeys: make(map[uint64]*symmetric.Key), - userStates: make(map[string]*aclpb.ACLChangeUserState), - userInvites: make(map[string]*aclpb.ACLChangeUserInvite), - permissionsAtRecord: make(map[string][]UserPermissionPair), + signingPubKeyDecoder: decoder, + userReadKeys: make(map[uint64]*symmetric.Key), + userStates: make(map[string]*aclpb.ACLChangeUserState), + userInvites: make(map[string]*aclpb.ACLChangeUserInvite), + permissionsAtRecord: make(map[string][]UserPermissionPair), } } diff --git a/pkg/acl/list/aclstatebuilder.go b/pkg/acl/list/aclstatebuilder.go index 3586dec9..f7a06f51 100644 --- a/pkg/acl/list/aclstatebuilder.go +++ b/pkg/acl/list/aclstatebuilder.go @@ -20,8 +20,10 @@ func newACLStateBuilderWithIdentity(decoder keys.Decoder, accountData *account.A } } -func newACLStateBuilder() *aclStateBuilder { - return &aclStateBuilder{} +func newACLStateBuilder(decoder keys.Decoder) *aclStateBuilder { + return &aclStateBuilder{ + decoder: decoder, + } } func (sb *aclStateBuilder) Build(records []*Record) (*ACLState, error) { @@ -30,10 +32,10 @@ func (sb *aclStateBuilder) Build(records []*Record) (*ACLState, error) { state *ACLState ) - if sb.decoder != nil { + if sb.key != nil { state = newACLStateWithIdentity(sb.identity, sb.key, sb.decoder) } else { - state = newACLState() + state = newACLState(sb.decoder) } for _, rec := range records { err = state.applyChangeAndUpdate(rec) diff --git a/pkg/acl/list/list.go b/pkg/acl/list/list.go index 54863bee..cf898271 100644 --- a/pkg/acl/list/list.go +++ b/pkg/acl/list/list.go @@ -6,6 +6,7 @@ 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/pkg/acl/storage" + "github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys" "sync" ) @@ -44,11 +45,24 @@ type aclList struct { func BuildACLListWithIdentity(acc *account.AccountData, storage storage.ListStorage) (ACLList, error) { builder := newACLStateBuilderWithIdentity(acc.Decoder, acc) + return buildWithACLStateBuilder(builder, storage) +} + +func BuildACLList(decoder keys.Decoder, storage storage.ListStorage) (ACLList, error) { + return buildWithACLStateBuilder(newACLStateBuilder(decoder), storage) +} + +func buildWithACLStateBuilder(builder *aclStateBuilder, storage storage.ListStorage) (ACLList, error) { header, err := storage.Header() if err != nil { return nil, err } + id, err := storage.ID() + if err != nil { + return nil, err + } + rawRecord, err := storage.Head() if err != nil { return nil, err @@ -94,6 +108,7 @@ func BuildACLListWithIdentity(acc *account.AccountData, storage storage.ListStor indexes: indexes, builder: builder, aclState: state, + id: id, RWMutex: sync.RWMutex{}, }, nil } @@ -114,7 +129,7 @@ func (a *aclList) IsAfter(first string, second string) (bool, error) { firstRec, okFirst := a.indexes[first] secondRec, okSecond := a.indexes[second] if !okFirst || !okSecond { - return false, fmt.Errorf("not all entries are there: first (%b), second (%b)", okFirst, okSecond) + return false, fmt.Errorf("not all entries are there: first (%t), second (%t)", okFirst, okSecond) } return firstRec >= secondRec, nil } diff --git a/pkg/acl/list/list_test.go b/pkg/acl/list/list_test.go new file mode 100644 index 00000000..60804186 --- /dev/null +++ b/pkg/acl/list/list_test.go @@ -0,0 +1,46 @@ +package list + +import ( + "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb" + "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/testutils/acllistbuilder" + "github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/signingkey" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "testing" +) + +func TestAclList_ACLState_UserInviteAndJoin(t *testing.T) { + st, err := acllistbuilder.NewListStorageWithTestName("userjoinexample.yml") + require.NoError(t, err, "building storage should not result in error") + + keychain := st.(*acllistbuilder.ACLListStorageBuilder).GetKeychain() + + aclList, err := BuildACLList(signingkey.NewEDPubKeyDecoder(), st) + require.NoError(t, err, "building acl list should be without error") + + idA := keychain.GetIdentity("A") + idB := keychain.GetIdentity("B") + idC := keychain.GetIdentity("C") + + // checking final state + assert.Equal(t, aclList.ACLState().GetUserStates()[idA].Permissions, aclpb.ACLChange_Admin) + assert.Equal(t, aclList.ACLState().GetUserStates()[idB].Permissions, aclpb.ACLChange_Writer) + assert.Equal(t, aclList.ACLState().GetUserStates()[idC].Permissions, aclpb.ACLChange_Reader) + assert.Equal(t, aclList.ACLState().CurrentReadKeyHash(), aclList.Head().Content.CurrentReadKeyHash) + var records []*Record + aclList.Iterate(func(record *Record) (IsContinue bool) { + records = append(records, record) + return true + }) + + // checking permissions at specific records + assert.Equal(t, 3, len(records)) + _, err = aclList.ACLState().PermissionsAtRecord(records[1].Id, idB) + assert.Error(t, err, "B should have no permissions at record 1") + perm, err := aclList.ACLState().PermissionsAtRecord(records[2].Id, idB) + assert.NoError(t, err, "should have no error with permissions of B in the record 2") + assert.Equal(t, perm, UserPermissionPair{ + Identity: idB, + Permission: aclpb.ACLChange_Writer, + }) +} diff --git a/pkg/acl/testutils/acllistbuilder/keychain.go b/pkg/acl/testutils/acllistbuilder/keychain.go index a1567f6a..7098650e 100644 --- a/pkg/acl/testutils/acllistbuilder/keychain.go +++ b/pkg/acl/testutils/acllistbuilder/keychain.go @@ -50,8 +50,8 @@ func (k *Keychain) ParseKeys(keys *Keys) { } } -func (k *Keychain) AddEncryptionKey(name string) { - if _, exists := k.EncryptionKeys[name]; exists { +func (k *Keychain) AddEncryptionKey(key *Key) { + if _, exists := k.EncryptionKeys[key.Name]; exists { return } newPrivKey, _, err := encryptionkey.GenerateRandomRSAKeyPair(2048) @@ -59,11 +59,11 @@ func (k *Keychain) AddEncryptionKey(name string) { panic(err) } - k.EncryptionKeys[name] = newPrivKey + k.EncryptionKeys[key.Name] = newPrivKey } -func (k *Keychain) AddSigningKey(name string) { - if _, exists := k.SigningKeys[name]; exists { +func (k *Keychain) AddSigningKey(key *Key) { + if _, exists := k.SigningKeys[key.Name]; exists { return } newPrivKey, pubKey, err := signingkey.GenerateRandomEd25519KeyPair() @@ -71,48 +71,47 @@ func (k *Keychain) AddSigningKey(name string) { panic(err) } - k.SigningKeys[name] = newPrivKey + k.SigningKeys[key.Name] = newPrivKey res, err := k.coder.EncodeToString(pubKey) if err != nil { panic(err) } k.SigningKeysByIdentity[res] = newPrivKey - k.GeneratedIdentities[name] = res + k.GeneratedIdentities[key.Name] = res } -func (k *Keychain) AddReadKey(name string) { - if _, exists := k.ReadKeys[name]; exists { +func (k *Keychain) AddReadKey(key *Key) { + if _, exists := k.ReadKeys[key.Name]; exists { return } - key, _ := symmetric.NewRandom() + rkey, _ := symmetric.NewRandom() hasher := fnv.New64() - hasher.Write(key.Bytes()) + hasher.Write(rkey.Bytes()) - k.ReadKeys[name] = &SymKey{ + k.ReadKeys[key.Name] = &SymKey{ Hash: hasher.Sum64(), - Key: key, + Key: rkey, } k.ReadKeysByHash[hasher.Sum64()] = &SymKey{ Hash: hasher.Sum64(), - Key: key, + Key: rkey, } } -func (k *Keychain) AddKey(key string) { - parts := strings.Split(key, ".") +func (k *Keychain) AddKey(key *Key) { + parts := strings.Split(key.Name, ".") if len(parts) != 3 { panic("cannot parse a key") } - name := parts[2] switch parts[1] { case "Sign": - k.AddSigningKey(name) + k.AddSigningKey(key) case "Enc": - k.AddEncryptionKey(name) + k.AddEncryptionKey(key) case "Read": - k.AddReadKey(name) + k.AddReadKey(key) default: panic("incorrect format") } diff --git a/pkg/acl/testutils/acllistbuilder/liststoragebuilder.go b/pkg/acl/testutils/acllistbuilder/liststoragebuilder.go index 1037770e..25c09586 100644 --- a/pkg/acl/testutils/acllistbuilder/liststoragebuilder.go +++ b/pkg/acl/testutils/acllistbuilder/liststoragebuilder.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb" + "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/storage" "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/testutils/yamltests" "github.com/anytypeio/go-anytype-infrastructure-experiments/util/cid" "github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/encryptionkey" @@ -34,7 +35,7 @@ func NewACLListStorageBuilder(keychain *Keychain) *ACLListStorageBuilder { } } -func NewACLListStorageBuilderWithTestName(name string) (*ACLListStorageBuilder, error) { +func NewListStorageWithTestName(name string) (storage.ListStorage, error) { filePath := path.Join(yamltests.Path(), name) return NewACLListStorageBuilderFromFile(filePath) } @@ -89,7 +90,7 @@ func (t *ACLListStorageBuilder) Header() (*aclpb.Header, error) { return t.header, nil } -func (t *ACLListStorageBuilder) GetRecord(ctx context.Context, id string) (*aclpb.RawRecord, error) { +func (t *ACLListStorageBuilder) GetRawRecord(ctx context.Context, id string) (*aclpb.RawRecord, error) { recIdx, ok := t.indexes[id] if !ok { return nil, fmt.Errorf("no such record") @@ -97,7 +98,7 @@ func (t *ACLListStorageBuilder) GetRecord(ctx context.Context, id string) (*aclp return t.getRecord(recIdx), nil } -func (t *ACLListStorageBuilder) AddRecord(ctx context.Context, rec *aclpb.Record) error { +func (t *ACLListStorageBuilder) AddRawRecord(ctx context.Context, rec *aclpb.RawRecord) error { panic("implement me") } diff --git a/pkg/acl/testutils/acllistbuilder/ymlentities.go b/pkg/acl/testutils/acllistbuilder/ymlentities.go index 471155c1..4d0fb069 100644 --- a/pkg/acl/testutils/acllistbuilder/ymlentities.go +++ b/pkg/acl/testutils/acllistbuilder/ymlentities.go @@ -1,9 +1,14 @@ package acllistbuilder +type Key struct { + Name string `yaml:"name"` + Value string `yaml:"value"` +} + type Keys struct { - Enc []string `yaml:"Enc"` - Sign []string `yaml:"Sign"` - Read []string `yaml:"Read"` + Enc []*Key `yaml:"Enc"` + Sign []*Key `yaml:"Sign"` + Read []*Key `yaml:"Read"` } type ACLChange struct { @@ -50,8 +55,7 @@ type ACLChange struct { type Record struct { Identity string `yaml:"identity"` AclChanges []*ACLChange `yaml:"aclChanges"` - - ReadKey string `yaml:"readKey"` + ReadKey string `yaml:"readKey"` } type Header struct { diff --git a/pkg/acl/testutils/acllistbuilder/ymlentities_test.go b/pkg/acl/testutils/acllistbuilder/ymlentities_test.go index 28780446..29d86d31 100644 --- a/pkg/acl/testutils/acllistbuilder/ymlentities_test.go +++ b/pkg/acl/testutils/acllistbuilder/ymlentities_test.go @@ -6,7 +6,7 @@ import ( ) func Test_YamlParse(t *testing.T) { - tb, _ := NewACLListStorageBuilderWithTestName("userjoinexampleupdate.yml") + tb, _ := NewListStorageWithTestName("userjoinexampleupdate.yml") gr, _ := tb.Graph() fmt.Println(gr) } diff --git a/pkg/acl/testutils/yamltests/userjoinexample.yml b/pkg/acl/testutils/yamltests/userjoinexample.yml index 9b4ac7f1..014c9349 100644 --- a/pkg/acl/testutils/yamltests/userjoinexample.yml +++ b/pkg/acl/testutils/yamltests/userjoinexample.yml @@ -1,5 +1,3 @@ -list: - author: A records: - identity: A aclChanges: @@ -16,7 +14,7 @@ records: encryptionKey: key.Enc.Onetime1 encryptedReadKeys: [key.Read.1] permissions: writer - inviteIdx: A.1.2 + inviteId: A.1.2 - userAdd: identity: C permission: reader diff --git a/pkg/acl/tree/doctree.go b/pkg/acl/tree/doctree.go index d0d373d4..4aa1b061 100644 --- a/pkg/acl/tree/doctree.go +++ b/pkg/acl/tree/doctree.go @@ -77,48 +77,27 @@ type docTree struct { } func BuildDocTreeWithIdentity(t storage.TreeStorage, acc *account.AccountData, listener TreeUpdateListener, aclList list.ACLList) (DocTree, error) { - treeBuilder := newTreeBuilder(t, acc.Decoder) - validator := newTreeValidator() - - docTree := &docTree{ - treeStorage: t, - accountData: acc, - tree: nil, - treeBuilder: treeBuilder, - validator: validator, - updateListener: listener, - tmpChangesBuf: make([]*Change, 0, 10), - difSnapshotBuf: make([]*aclpb.RawChange, 0, 10), - notSeenIdxBuf: make([]int, 0, 10), - identityKeys: make(map[string]signingkey.PubKey), - } - err := docTree.rebuildFromStorage(aclList, nil) - if err != nil { - return nil, err - } - docTree.id, err = t.ID() - if err != nil { - return nil, err - } - docTree.header, err = t.Header() - if err != nil { - return nil, err - } - - if listener != nil { - listener.Rebuild(docTree) - } - - return docTree, nil + return buildDocTreeWithAccount(t, acc, acc.Decoder, listener, aclList) } func BuildDocTree(t storage.TreeStorage, decoder keys.Decoder, listener TreeUpdateListener, aclList list.ACLList) (DocTree, error) { + return buildDocTreeWithAccount(t, nil, decoder, listener, aclList) +} + +func buildDocTreeWithAccount( + t storage.TreeStorage, + acc *account.AccountData, + decoder keys.Decoder, + listener TreeUpdateListener, + aclList list.ACLList) (DocTree, error) { + treeBuilder := newTreeBuilder(t, decoder) validator := newTreeValidator() docTree := &docTree{ treeStorage: t, tree: nil, + accountData: acc, treeBuilder: treeBuilder, validator: validator, updateListener: listener,